結論:handleとonEndで並べ替え・保存する
SortableJSでリストをドラッグ&ドロップで並べ替えたい場合は、CDNをバージョン固定で読み込み、対象の<ul>をnew Sortable()に渡します。ドラッグの起点を専用アイコンに絞りたいときはhandleを、並べ替えた結果を保存したいときはonEndを使います。
<ul id="list"> <li data-id="1"><span class="handle">⠿</span>Aタスク</li> <li data-id="2"><span class="handle">⠿</span>Bタスク</li> <li data-id="3"><span class="handle">⠿</span>Cタスク</li> </ul> <script src="https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js"></script> <script> var list = document.getElementById('list'); new Sortable(list, { handle: '.handle', ghostClass: 'is-ghost', animation: 150, onEnd: function () { var order = Array.from(list.children).map(function (li) { return li.dataset.id; }); localStorage.setItem('list-order', JSON.stringify(order)); } }); </script>
これだけで、ハンドルをつかんだときだけドラッグが始まり、並べ替えが終わるたびに現在の順序(IDの配列)がlocalStorageへ保存されます。読み込み時に保存値があればその順でリストを描画すれば、リロードしても並び順が復元されます。
動くデモ
ハンドルをつかんでドラッグすると並べ替えができ、並べ替えるたびに並び順が保存されます。ブラウザをリロードしても保存した並び順のままです。スマートフォンでもハンドルをタッチしてドラッグすれば同じように並べ替えられます。
オプションの役割
今回使ったオプションは多くありません。まずは次の4つを押さえれば、ハンドル指定と並び順保存の基本は完成します。
| オプション | 役割 |
|---|---|
handle |
ドラッグの起点にする要素をCSSセレクタで指定する。指定した要素以外をつかんでもドラッグは始まらない。 |
ghostClass |
ドラッグ中のアイテムに付与されるクラス名。ここで半透明表示などドラッグ中の見た目を指定する。 |
animation |
並べ替え時のアニメーション時間(ミリ秒)。0にすると即座に切り替わる。 |
onEnd |
ドラッグ操作が終わったときに呼ばれるコールバック。並び順の取得・保存はここで行う。 |
並び順を保存する際は、onEnd内で子要素のdata-id属性を順番に取得し、JSON.stringifyしてlocalStorageに保存します。プライベートブラウジング等でlocalStorageが使えない環境もあるため、保存処理はtry-catchで囲み、失敗しても並べ替え自体は動くようにしておくと安全です。CDNのURLは@1.15.7のようにバージョン番号を固定し、@latestは使わないようにします。ライブラリが更新されてもコードの挙動が予告なく変わらないようにするためです。
よくあるハマりどころ
スマホでドラッグしようとすると画面がスクロールしてしまう
SortableJSは標準でタッチ操作に対応していますが、リストが縦に長い画面では、ハンドルへのタッチが画面スクロールと競合して並べ替えが始まりにくく感じることがあります。公式ドキュメントでは、この種の競合への対処としてforceFallback(ネイティブのドラッグ実装ではなく独自実装に切り替える)とdelayOnTouchOnly(タッチ操作時のみ、ドラッグ開始までに一定の押下時間を要求する)が用意されています。
new Sortable(list, {
handle: '.handle',
delayOnTouchOnly: true,
delay: 150
});
delayOnTouchOnly: trueとdelayを組み合わせると、指を置いてすぐの縦スクロールは通常通り機能し、一定時間ハンドルを押し続けたときだけドラッグが始まるようになります。どの設定が適切かは画面のスクロール量やリストの長さによって変わるため、実際の端末で挙動を確認してから調整するのが安全です。
テーブルの行にも使えるか
SortableJSは<ul>/<li>に限らず、<tbody>を対象にしてテーブルの行(<tr>)を並べ替えることもできます。初期化の書き方自体はリストと同じで、対象要素をtbodyに変えるだけです。詳しいオプションは公式リポジトリのドキュメントを参照してください。
一次情報の確認先
この記事では、SortableJS公式のGitHubリポジトリとREADMEのオプション一覧、jsDelivrの配信ページを確認しています。実装時点でのバージョンは、動くサンプルである「リスト — ライブラリ版並べ替え」と同じ1.15.7に揃えています。
まとめ
SortableJSでハンドル指定の並べ替えと並び順の保存を実装するには、handle・ghostClass・onEndの3つを押さえれば十分です。スマホでのスクロール競合が気になる場合はdelayOnTouchOnlyを、テーブル行を並べ替えたい場合は対象をtbodyに変える形で応用できます。
タッチ対応の実演やlocalStorageへの保存・復元、自作版(HTML5 Drag & Drop API)との方式比較まで含めた実装は「リスト — ライブラリ版並べ替え」で確認できます。ライブラリを使わずに自分で実装したい場合は「リスト — ドラッグ&ドロップ並べ替え」も参考にしてください。