この記事では、dnd-kitを使ってドラッグ&ドロップで並び替えを実装する方法を紹介します。
1から実装するのはかなり大変ですが、ライブラリを使うと簡単に並び替えを実装することができます。
その中でも、dnd-kitは導入も簡単で、オプションも充実しており自分も使ってみて使いやすかったので共有です。
React(+TypeScript)で実装しているので同じ状況で気になっている方の参考になれば幸いです。
この記事を読むメリット
- ドラッグ&ドロップでの並び替え実装ができるようになる
- dnd-kitの基本的な使い方が分かる
dnd-kitを使ってドラッグ&ドロップで並び替えを実装する方法
Reactの環境はすでにある前提で進めていきます。
dnd-kitのインストール
まずはdnd-kitで必要なパッケージをインストールします。
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
package.jsonで追加されていればOKです。
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
"@dnd-kit/utilities": "^3.2.2",
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
tailwindcssのインストール
ついでにtailwindcssもインストールしておきます。
npm install tailwindcss @tailwindcss/vite
vite.config.tsにtailwindcssを追加します。
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
// https://vite.dev/config/
export default defineConfig({
plugins: [react(), tailwindcss()],
});
あとは使いたいcssファイルでインポートします。
@import 'tailwindcss';
tailwindcssのインストール方法がバージョン4以降で変わったみたいで、4以降の方はこちらの方法でお試しください。
今までの方法だとエラーが出ます。
並び替えのリストを実装
ここから実際にdnd-kitを使って並び替えを実装していきます。
まずは完成コードから。
import { DndContext, closestCenter } from '@dnd-kit/core';
import {
SortableContext,
useSortable,
verticalListSortingStrategy,
arrayMove,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { useState } from 'react';
const SortableItem = ({ id }: { id: string }) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<li
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
className={`w-full border rounded-md px-4 py-2 bg-white shadow-sm cursor-move ${
isDragging && 'opacity-50'
}`}
>
{id}
</li>
);
};
export default function SortableList() {
const [items, setItems] = useState(['リスト1', 'リスト2', 'リスト3', 'リスト4', 'リスト5']);
const handleDragEnd = (event: any) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
setItems(arrayMove(items, oldIndex, newIndex));
}
};
return (
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<ul className="space-y-2">
{items.map((id) => (
<SortableItem key={id} id={id} />
))}
</ul>
</SortableContext>
</DndContext>
);
}
画面の動きはこんな感じになります。
正常にドラッグ&ドロップで並び替えができているかと思います。
次章で実装したコードを1つずつ確認してみたいと思います。
dnd-kitの並び替え実装の中身解説
それでは先ほどのコードの中身を1つずつみていきたいと思います。
並び替え可能なアイテムコンポーネントSortableItem
まずは並び替えができるアイテムコンポーネントである「SortableItem」を定義します。
const SortableItem = ({ id }: { id: string }) => {
const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
id,
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
return (
<li
ref={setNodeRef}
style={style}
{...attributes}
{...listeners}
className={`w-full border rounded-md px-4 py-2 bg-white shadow-sm cursor-move ${
isDragging && 'opacity-50'
}`}
>
{id}
</li>
);
};
「useSortable({ id }) 」によって、このアイテムがドラッグ可能になり、ドラッグ操作に必要な情報や関数がまとめて手に入ります。
それぞれの簡単な説明が以下になります。
- setNodeRef
→ この関数を要素(例:<li>
)に渡すことで、「この要素をドラッグできる対象」として認識させることができます。DOMとのつながりを設定するイメージです。 - attributes
→ キーボード操作やアクセシビリティのために必要な属性(role や tabIndex など)を自動で設定してくれます。そのまま<li>
に渡します。 - listeners
→ マウスやタッチでのドラッグ操作に必要なイベント(onPointerDownなど)をまとめて持っています。これもそのまま<li>
に付ければ、ドラッグできるようになります。 - transform
→ 要素がドラッグ中に「どこに動いているか」を表す値です。x座標、y座標の位置情報が含まれていて、これを使って要素の位置をリアルタイムに変化させます。 - transition
→ 要素の動きになめらかなアニメーションをつけるために使います。「ピタッと元の位置に戻る」動きなどがこれで制御できます。 - isDragging
→ この要素が今まさにドラッグされている状態かどうか(true / false)がわかります。ドラッグのみに独自のスタイルを当てたりすることができます。
あともう1つ、styleでドラッグ中の要素の見た目を定義します。
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
transformとtransitionを定義することで、スムーズにドラッグ&ドロップができるようにスタイルが当たるようになります。
これで並び替え可能なコンポーネントを作成できました。
並び替え要素のリストを定義
続いてが並び替え要素のリストを定義します。
const [items, setItems] = useState(['リスト1', 'リスト2', 'リスト3', 'リスト4', 'リスト5']);
ここは特にdnd-kit特有のものではないので説明は割愛します。
実際にはバックエンドなどから取得した配列データになるかと思います。
並び替え完了時の処理「handleDragEnd」
続いてが並び替え完了時の処理である「handleDragEnd」を定義します。
const handleDragEnd = (event: any) => {
const { active, over } = event;
if (active.id !== over?.id) {
const oldIndex = items.indexOf(active.id);
const newIndex = items.indexOf(over.id);
setItems(arrayMove(items, oldIndex, newIndex));
}
};
これはドラッグが完了した際に呼ばれる関数です。
並び替えが発生していたら、arrayMove()関数を使って配列を更新します。
描画部分
最後が今までのものを使って描画するコードになります。
return (
<DndContext collisionDetection={closestCenter} onDragEnd={handleDragEnd}>
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<ul className="space-y-2">
{items.map((id) => (
<SortableItem key={id} id={id} />
))}
</ul>
</SortableContext>
</DndContext>
);
DndContextでリスト全体を囲みます。
「onDragEnd={handleDragEnd}」で先ほどのドラッグ完了時の動きを定義しています。
collisionDetectionはドラッグしているアイテムがどのアイテムと重なっているかの判定方法の種類のイメージです。
「closestCenter」以外にも、rectIntersection や pointerWithinなどがあるようです。
そしてその中にSortableContextを配置して、itemsにリスト配列を指定します。
verticalListSortingStrategy により「縦方向の並び替え」ができるようになります。
あとは中身でSortableItemをレンダリングして完了です。
dnd-kitで簡単にドラッグ&ドロップで並び替えが実装できる
いかがだったでしょうか。
dnd-kitを使うととても簡単に実装ができるかと思います。
さらにオプションで特定の並び替えができるアイコンをつけて、そこの部分だけが操作可能範囲とすることもできたりします。
ぜひ公式も参考にしつつ実装してみてください。