はじめに
「カードの並べ替え機能を付けたい」——そう思ってドラッグ&ドロップライブラリを探し始めると、途端に選択肢の多さと設定の複雑さに頭を抱えることになります。React専用のライブラリを選べばVueのプロジェクトでは使えませんし、多機能なライブラリを選べば今度はAPIの学習コストが重くのしかかってきます。
そんな悩みを一気に解決してくれるのが、今回紹介するSwapyです。HTMLにdata属性を数個書くだけで、どんなレイアウトもドラッグでスワップできるようになる、シンプルさが売りのライブラリです。
とはいえ、読むより触った方が早いと思います。実際にドラッグして要素を入れ替えられるサンプルを用意したので、先に挙動を見たい方はこちらからどうぞ。
Swapyとは
Swapyは、"A framework-agnostic tool that converts any layout into a drag-to-swap one with just a few lines of code"(フレームワークに依存せず、数行のコードでレイアウトをドラッグ・スワップ対応にするツール)を謳うJavaScriptライブラリです。GitHub上で8,500以上のスターを集めており、React・Vue・Svelteそれぞれの統合ガイドも公式に用意されています。
一般的なドラッグ&ドロップライブラリが「並べ替え(ソート)」を主眼に置いているのに対し、Swapyは「スワップ(入れ替え)」に特化しているのが特徴です。要素をドラッグして別の場所にドロップすると、両者の位置がそのまま入れ替わる、という直感的な挙動を実現します。
主な特徴
- フレームワーク非依存 - Vanilla JSはもちろん、React・Vue・Svelteでも同じ考え方で組み込める
- HTML属性ベースの宣言的API -
data-swapy-slotとdata-swapy-itemを書くだけで対象を指定できる - アニメーション設定が簡単 -
dynamic・spring・noneの3種類から切り替え可能 - 豊富なイベントとメソッド -
onSwapなどのイベントに加え、update()やdestroy()で状態管理もしやすい
インストール
npm・pnpm・yarnのいずれでもインストールできます。
npm install swapy
pnpm install swapy
yarn add swapy
ビルドツールを使わない場合は、CDN経由での読み込みも可能です。
<script src="https://unpkg.com/swapy/dist/swapy.min.js"></script>
<script>
const swapy = Swapy.createSwapy(container)
</script>
Swapyのサンプルを動かす
下のカードA・B・CはcreateSwapy()でSwapyのインスタンス化を行った要素です。マウスやタッチでカードをドラッグし、別のスロットにドロップしてみてください。入れ替えが起きるたびにonSwapイベントが発火し、その場で新しい配置が表示されます。
Swapyの本質は「枠(data-swapy-slot)」と「中身(data-swapy-item)」をHTML属性で紐付けるだけというシンプルさにあります。要点だけを抜き出すと次のようになります。
<div class="container">
<div data-swapy-slot="a">
<div data-swapy-item="a"><div>A</div></div>
</div>
<div data-swapy-slot="b">
<div data-swapy-item="b"><div>B</div></div>
</div>
</div>
import { createSwapy } from 'swapy'
const swapy = createSwapy(document.querySelector('.container'), {
animation: 'dynamic'
})
swapy.onSwap((event) => {
console.log(event.newSlotItemMap.asObject)
})
実際に動かせるサンプルが下です。カードをドラッグして入れ替えると、event.newSlotItemMap.asObjectから取得した最新の配置が画面上のテキストに反映されます。
CとAを入れ替えると、テキストが{ a: 'c', b: 'b', c: 'a' }のように変化するのが確認できるはずです。コード中のanimation: 'dynamic'を'spring'や'none'に書き換えて再実行すると、ドラッグ中の動きの質感が変わります。data-swapy-slotとdata-swapy-itemの対応を増減させれば、そのままスロット数を4つ、5つと拡張することも可能です。
基本的な使い方
Swapyでは、入れ替え可能な「枠」をdata-swapy-slot、入れ替えられる「中身」をdata-swapy-itemとして定義します。
<div class="container">
<!-- Slot A -->
<div data-swapy-slot="a">
<!-- Item A -->
<div data-swapy-item="a">
<div>A</div>
</div>
</div>
<!-- Slot B -->
<div data-swapy-slot="b">
<!-- Item B -->
<div data-swapy-item="b">
<div>B</div>
</div>
</div>
<!-- Slot C -->
<div data-swapy-slot="c">
<!-- Item C -->
<div data-swapy-item="c">
<div>C</div>
</div>
</div>
</div>
HTMLを用意したら、コンテナ要素を取得してcreateSwapy()に渡すだけです。
import { createSwapy } from 'swapy'
const container = document.querySelector('.container')
const swapy = createSwapy(container, {
animation: 'dynamic' // 'spring' や 'none' も選択可能
})
入れ替えが発生したタイミングを検知したい場合は、onSwapイベントを利用します。
swapy.onSwap((event) => {
console.log(event.newSlotItemMap.asObject)
// { a: 'a', b: 'c', c: 'b' } のような形式で新しい配置が取得できる
console.log(event.fromSlot) // ドラッグ元のスロット
console.log(event.toSlot) // ドラッグ先のスロット
})
コンポーネントの破棄時には、必ずdestroy()を呼び出してイベントリスナーを解放しましょう。
swapy.destroy()
実践的なユースケース
Reactでのダッシュボードウィジェット並べ替え
実際の開発でよくあるのが、Reactコンポーネント内でSwapyを使うケースです。DOMが確実にマウントされた後にインスタンスを生成する必要があるため、useEffectとクリーンアップ処理を組み合わせます。
import { createSwapy, Swapy } from 'swapy'
import { useEffect, useRef } from 'react'
function Dashboard() {
const swapy = useRef<Swapy | null>(null)
const container = useRef<HTMLDivElement>(null)
useEffect(() => {
if (container.current) {
swapy.current = createSwapy(container.current, {
animation: 'dynamic'
})
swapy.current.onSwap((event) => {
// 並び順をlocalStorageに保存しておけば、次回アクセス時にも復元できる
localStorage.setItem(
'dashboard-layout',
JSON.stringify(event.newSlotItemMap.asObject)
)
})
}
return () => {
swapy.current?.destroy()
}
}, [])
return (
<div ref={container} className="dashboard-grid">
<div data-swapy-slot="chart">
<div data-swapy-item="chart">
<div className="widget">売上グラフ</div>
</div>
</div>
<div data-swapy-slot="table">
<div data-swapy-item="table">
<div className="widget">注文一覧</div>
</div>
</div>
<div data-swapy-slot="summary">
<div data-swapy-item="summary">
<div className="widget">サマリー</div>
</div>
</div>
</div>
)
}
このように、ユーザーが自由にウィジェットの配置を入れ替えられるダッシュボードや、タスク管理ツールのカラム内でカードの位置を入れ替えるカンバンボードなど、「決まった枠の中身を入れ替えたい」場面で威力を発揮します。
動的に要素の追加・削除を行う場合は、swapy.update()を呼び出してSwapyに変更を再認識させることも忘れないようにしましょう。
function afterAddWidget() {
swapy.update()
}
アニメーションモードを切り替える
SwapyはcreateSwapy()のオプションでanimationをdynamic・spring・noneの3種類から選べます。ただしanimationはインスタンス生成時にしか指定できないため、途中で切り替えたい場合は一度destroy()してから、新しいオプションでcreateSwapy()をやり直す必要があります。
const swapy = createSwapy(container, {
animation: 'dynamic' // 'spring' | 'none' も指定可能
})
// animationを切り替えたいときは作り直す
swapy.destroy()
const next = createSwapy(container, { animation: 'spring' })
下のサンプルでは、上部のボタンでdynamic・spring・noneを切り替えられます。ボタンを押すたびに既存のSwapyインスタンスをdestroy()し、選んだanimation値でcreateSwapy()し直しています。カード(1・2・3)をドラッグして、モードごとにドラッグ中の動きの質感がどう変わるか比べてみてください。
noneを選ぶとアニメーションなしで瞬時に入れ替わり、springを選ぶとバネのような弾みが付いた動きになります。用途に応じて、ダッシュボードのようにきびきび動かしたい場面ではdynamic、遊び心を出したい場面ではspring、といった使い分けができます。
まとめ
Swapyは、複雑な設定なしにドラッグ&ドロップによる「入れ替えUI」を実現できる、非常にとっつきやすいライブラリです。data-swapy-slotとdata-swapy-itemという2つの属性さえ覚えてしまえば、Vanilla JSでもReactでも同じ感覚で実装できます。
ダッシュボードのカスタマイズ機能やカンバンボードなど、「要素の位置を入れ替えたい」という要件に直面したときは、まずSwapyを試してみてはいかがでしょうか。公式ドキュメントには他にもハンドル要素の指定やドラッグ軸の制限など、細かなカスタマイズオプションが用意されているので、あわせて確認してみてください。
