はじめに
ドラッグ&ドロップのUIを自作しようとして、mousedown / mousemove / mouseup とタッチイベントの分岐、さらにHTML5のdragstart / dragover / dropとの整合性に頭を抱えたことはありませんか。マウスとタッチでイベント名も座標の取り方も違ううえ、並べ替え・スワップ・ドロップ判定まで自前で組むとなると、あっという間にコードが複雑化してしまいます。
Shopifyが開発しているDraggableは、こうしたマウス・タッチ・力覚タッチ(Force Touch)のイベントを統一されたAPIに抽象化し、ドラッグ&ドロップの実装をぐっと楽にしてくれるライブラリです。単なる「要素をつかんで動かす」だけでなく、並べ替え(Sortable)・入れ替え(Swappable)・ドロップ先への振り分け(Droppable)といった、実務でよくあるパターンごとにモジュールが分かれているのが特徴です。
とはいえ、読むより触った方が早いと思います。ドラッグ操作に応じてイベントが発火する様子を実際に見られるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
Draggableとは
DraggableはShopifyが開発・公開しているJavaScript製のドラッグ&ドロップライブラリです。GitHub上で18,000以上のスターを集めており、公式リポジトリは今も更新が続いています。ブラウザネイティブのマウス・タッチ・force touchイベントを、drag:start / drag:move / drag:stopのような一貫したイベント群に変換してくれるため、デバイスごとの分岐処理を書かずに済みます。
主な特徴
- 統一されたイベントAPI - マウスでもタッチでも同じイベント名・同じデータ構造でハンドリングできるため、デバイス差分を意識せずに実装できます
- 用途別に分かれたモジュール - 並べ替えの
Sortable、要素の入れ替えのSwappable、ドロップ先への振り分けのDroppableなど、やりたいことに応じてクラスを選べます - プラグインによる拡張 - ドラッグ中の見た目を制御する
Mirror、スクロール追従のScrollable、衝突判定のCollidableなど、必要な機能だけを組み合わせられます
インストール
npmまたはyarnでインストールできます。
npm install @shopify/draggable --save
yarn add @shopify/draggable
ESM・UMDのビルド済みファイルも同梱されているため、CDN経由での読み込みにも対応しています。
Draggableのサンプルを動かす
まずはDraggableの基本クラスであるDraggableを使って、要素をドラッグしたときにdrag:start・drag:move・drag:stopイベントがどう発火するかを確かめてみましょう。ドラッグ中は分身要素(ミラー)が表示され、実体はその場に留まったまま移動先を確認できます。
コアとなる書き方は次のとおりです。コンテナ要素と、ドラッグ対象とするセレクタを渡すだけで動き出します。
import { Draggable } from '@shopify/draggable'
const containers = document.querySelectorAll('.container')
const draggable = new Draggable(containers, {
draggable: '.item',
})
draggable.on('drag:start', () => console.log('ドラッグ開始'))
draggable.on('drag:stop', () => console.log('ドラッグ終了'))
下のサンプルではカード要素をドラッグすると、発火したイベント名がログとして画面に表示されます。カードをつかんで動かし、離してみてください。drag:start・drag:move・drag:stopが順番に記録されるのが確認できるはずです。
mirror.constrainDimensionsをfalseにすると、ドラッグ中の分身要素のサイズ制約が外れる違いも試せます。イベントの引数(event.sourceやevent.originalSource)をconsole.logで覗いてみると、どの要素がドラッグされたかを詳しく取得できることも分かります。
基本的な使い方
Draggableの最小構成は、コンテナ要素とドラッグ対象のセレクタを渡すだけです。イベントリスナーを追加しなくても、この時点でドラッグ&ドロップの見た目(ミラー表示)は動作します。
import { Draggable } from '@shopify/draggable'
const containers = document.querySelectorAll('.draggable-container')
const draggable = new Draggable(containers, {
draggable: '.draggable-item',
delay: 150, // ドラッグ開始までの遅延(ms)。誤操作防止に使える
distance: 8, // この距離(px)以上動かさないとドラッグと判定しない
})
Draggableクラス自体は「つかんで動かす」機能だけを提供します。並べ替えやドロップ判定など、具体的な用途に応じた振る舞いは、このDraggableを継承したSortableやSwappable、Droppableといったサブクラスが担当します。次の章では、それぞれの使い分けを実際のサンプルで見ていきます。
実践的なユースケース
Sortableでリストを並べ替える
タスクリストやカンバンボードのように、ドラッグした要素を好きな位置に差し込んで並べ替えたい場合はSortableクラスを使います。ドロップした瞬間にDOMの順序が入れ替わり、sortable:sortedイベントで並び替えの完了を検知できます。
import { Sortable } from '@shopify/draggable'
const sortable = new Sortable(document.querySelectorAll('.list'), {
draggable: '.list-item',
})
sortable.on('sortable:sorted', (event) => {
console.log('並び替え完了', event.newIndex)
})
下のサンプルはタスクの優先順位リストです。項目をドラッグして順番を入れ替えると、現在の並び順がリアルタイムで下部に表示されます。
sortable:sortedイベントのevent.oldIndexとevent.newIndexを使えば、並び替え後のインデックスをそのままAPIリクエストのペイロードに渡すような実装もしやすくなります。
Swappableで要素を入れ替える
並べ替えと違い、「AとBの位置をそのまま交換する」動きが欲しい場合はSwappableが適しています。座席表やカードゲームのスロットのように、要素数を保ったまま位置だけを入れ替えたいケースで使います。
import { Swappable } from '@shopify/draggable'
const swappable = new Swappable(document.querySelectorAll('.grid'), {
draggable: '.grid-item',
})
swappable.on('swappable:swapped', (event) => {
console.log('入れ替え', event.dragEvent.source, event.swappedElement)
})
下のサンプルは4マスのグリッドです。ブロックをドラッグして別のマスに重ねると、間に挟まれた要素と位置がまるごと交換されます(並べ替えのように前後にずれるのではなく、2要素だけが入れ替わる点に注目してください)。
swappable:swappedイベントは、入れ替え元(event.dragEvent.source)と入れ替え先(event.swappedElement)の両方を渡してくれるので、入れ替え後の状態をアプリ側の状態管理に反映する処理も書きやすくなっています。
Droppableでドロップ先ごとに振り分ける
ファイルをフォルダに仕分けたり、タスクをステータス別のカラムに移動したりする場合はDroppableが向いています。ドラッグ元とは別に「ドロップ可能な領域」を定義できるのが特徴です。
import { Droppable } from '@shopify/draggable'
const droppable = new Droppable(document.querySelectorAll('.container'), {
draggable: '.draggable-item',
dropzone: '.dropzone',
})
droppable.on('droppable:dropped', (event) => {
console.log('ドロップ先', event.dropzone)
})
下のサンプルでは、左側のタスクを右側の「未対応」「対応中」「完了」いずれかの枠にドロップすると、そのタスクが枠内に移動します。droppable:droppedイベントでドロップ先の要素を取得している点に注目してください。
ドロップゾーンを増減させたいだけなら、dropzoneセレクタに一致する要素をHTML側に追加・削除するだけで済みます。カラム構成をデータ駆動で作るカンバンUIとも相性が良い設計です。
まとめ
Draggableは、マウス・タッチ・force touchというデバイスごとのイベント差分を意識せずにドラッグ&ドロップを実装できるライブラリです。素のDraggableクラスでイベントの流れを押さえたうえで、並べ替えならSortable、位置の交換ならSwappable、仕分けならDroppableと、用途に応じてクラスを選ぶだけで実装できるのは大きな強みです。さらにMirrorやScrollable、Collidableといったプラグインを組み合わせれば、細かい見た目や挙動の調整も可能です。
タスク管理ボードやファイル整理UI、座席表のようなドラッグ操作が必要になったときは、自前でイベントを組む前に一度Draggableを試してみることをおすすめします。