ドラッグ&ドロップ は何をしますか?
ルール
@dnd-kit/core + @dnd-kit/sortable を使用 — モダンでアクセシブル、タッチ操作に対応した DnD ライブラリ
- ソート可能なリスト:
<SortableContext> でラップし、各アイテムで useSortable() フックを使用
- カンバンボード: 1つの
<DndContext> 内に複数の <SortableContext> コンテナ(カラムごとに1つ)を配置
- ファイルドロップゾーン:
react-dropzone またはネイティブの onDragOver / onDrop と e.dataTransfer.files を使用
- タッチサポート: dnd-kit がタッチをネイティブに処理 — アクティベーション制約付きの
TouchSensor および PointerSensor を使用
- アクセシビリティ: dnd-kit はキーボード DnD を標準で提供 —
sortableKeyboardCoordinates と組み合わせた KeyboardSensor を使用
- オプティミスティックな並べ替え: ドロップ時に即座に状態を更新、バックグラウンドでサーバーに永続化、エラー時にはロールバック
- ドラッグオーバーレイ:
<DragOverlay> を使用してドラッグ中のアイテムの浮動プレビューを表示 — より良い視覚的フィードバック
パターン
import { DndContext, closestCenter, DragOverlay } from "@dnd-kit/core";
import { SortableContext, verticalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
function SortableItem({ id, children }: { id: string; children: React.ReactNode }) {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id });
const style = { transform: CSS.Transform.toString(transform), transition };
return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners}>
{children}
</div>
);
}
function SortableList({ items }: { items: Item[] }) {
const [activeId, setActiveId] = useState<string | null>(null);
return (
<DndContext
collisionDetection={closestCenter}
onDragStart={({ active }) => setActiveId(active.id)}
onDragEnd={handleDragEnd}
>
<SortableContext items={items.map((i) => i.id)} strategy={verticalListSortingStrategy}>
{items.map((item) => (
<SortableItem key={item.id} id={item.id}>{item.name}</SortableItem>
))}
</SortableContext>
<DragOverlay>{activeId ? <ItemPreview id={activeId} /> : null}</DragOverlay>
</DndContext>
);
}
避けるべきこと
- HTML5 のドラッグ API を直接使用する — タッチ/モバイル/アクセシビリティのサポートが不十分
- キーボードアクセシビリティを忘れる — 常に
KeyboardSensor を含める
- ドラッグ中に状態を変更する —
onDragEnd でのみ更新
DragOverlay を省略する — これがないと、ドラッグ中に元のアイテムが視覚的フィードバックなしで消えてしまう