0.はじめに
Gemini-CLIでTrelloクローンアプリを作成した際に、
カンバンのドラッグ&ドロップ機能で、
dnd-kitを採用しました。
実装自体をGemini-CLIにまかせてしまったので、
内容を理解するために、記事を作成することにしました。
1.dnd-kitとは
Reactアプリケーション向けのモダンで軽量なドラッグ&ドロップライブラリ。
カンバンボードのような相互作用のあるUIを簡単に実装することが可能。
1.1 主な特徴
【軽量性】
- コアライブラリは約10KBと非常に軽量
コア部分は非常に小さく、約10KBの少量サイズ。
そのため、アプリケーションのバンドルサイズを最小限に抑えることができ、
ページ読み込み速度の向上が可能となる。
- 外部依存がない自己完結型の設計
外部ライブラリに依存せず、自己完結型で設計されているため、
他のライブラリとの競合を避け、安定した動作を保証。
【React最適化】
- React Hooksベースの直感的なAPI
ReactのHooksを活用し、
useDraggableやuseDroppableなどのフックを提供
Reactコンポーネント内でドラッグ&ドロップ機能を簡単に実装可能となっている。
- TypeScript完全対応
TypeScriptを使用することで、型安全性を確保しつつ、
開発者が効率的にコーディングできるようサポート。
dnd-kitはTypeScriptでの使用を前提に設計されており、
型定義が豊富に用意されている。
【豊富な機能】
- リスト、グリッド、複数コンテナ対応
リストやグリッド、複数のコンテナ間でのドラッグ&ドロップをサポート。
複雑なUIレイアウトでも柔軟に対応可能。
- ネストされたドラッグ&ドロップ
ネストされた構造(例えば、リスト内のリスト)でも
直感的な操作でドラッグ&ドロップが可能。
【高いカスタマイズ性】
- アニメーション・トランジション制御
ドラッグ中やドロップ後のアニメーションをカスタマイズすることができ、
よりスムーズで視覚的に魅力的なユーザー体験を提供。
2.プロジェクト作成・インストール
※本記事では下記3つのみとなりますが、
@dnd-kit/modifiersなどのライブラリもあります。
# Next.jsプロジェクト作成
npx create-next-app@latest dnd-kit-example
cd dnd-kit-example
# dnd-kitライブラリ一式インストール
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
@dnd-kit/core
ドラッグ&ドロップ機能のコアライブラリ
@dnd-kit/sortable
並び替え(Sortable)機能を提供するためのライブラリ
@dnd-kit/utilities
ドラッグ&ドロップの実装を助けるユーティリティ関数が含まれたライブラリ。
3.サンプルコンポーネント作成
3.1 カード作成
"use client";
import React from "react";
import { useSortable } from "@dnd-kit/sortable";
import { CSS } from "@dnd-kit/utilities";
type Props = {
id: string;
};
export function SortableItem({ id }: Props) {
// useSortableフックから並び替えに必要な情報を取得
const {
attributes,
listeners,
setNodeRef,
transform,
transition,
isDragging,
} = useSortable({ id: id });
// transformとtransitionをCSSのスタイルとして適用
const style = {
transform: CSS.Transform.toString(transform),
transition,
// ドラッグ中は少し見た目を変える
opacity: isDragging ? 0.5 : 1,
zIndex: isDragging ? 10 : "auto",
};
return (
// setNodeRefでこのdiv要素をdnd-kitが監視するDOMノードとして設定
<div ref={setNodeRef} style={style} className="p-4 border rounded bg-white mb-2 touch-none">
{/*
attributesとlistenersをハンドル(掴む部分)に適用する
今回はアイテム全体を掴めるようにdivに適用
*/}
<div {...attributes} {...listeners}>
アイテム {id}
</div>
</div>
);
}
useSortable(Hook)
並び替え可能なアイテムを作成するためのHooks
attributes:
roleやaria-属性など、ドラッグ可能な要素に付与すべき
アクセシビリティ関連の属性が含まれる
listeners:
ドラッグ操作を開始するためのイベントリスナー(onMouseDown, onTouchStart など)。
こちらを適用した要素がドラッグのハンドル(掴む部分)になります。
setNodeRef
dnd-kitがDOM要素を認識し、操作するために使用。
refプロパティに渡します。
transform
ドラッグ中の要素の移動量を表す {x, y} 形式のオブジェクト。
CSS.Transform.toString()を使ってCSSのtransformプロパティに変換する。
transition
ドラッグが終了し、要素が元の位置に戻る際のアニメーションを指定する。
isDragging
アイテムが現在ドラッグされているかどうかを判定するフラグ
3.2 リスト作成
"use client";
import React, { useState } from "react";
import {
DndContext,
closestCenter,
KeyboardSensor,
PointerSensor,
useSensor,
useSensors,
DragEndEvent,
} from "@dnd-kit/core";
import {
arrayMove,
SortableContext,
sortableKeyboardCoordinates,
verticalListSortingStrategy,
} from "@dnd-kit/sortable";
import { SortableItem } from "./SortableItem";
export function SortableList() {
// 並び替えるアイテムのリストをstateで管理
const [items, setItems] = useState(["1", "2", "3"]);
// センサー(どのような入力でドラッグを開始するか)の設定
// ここではポインター(マウスやタッチ)とキーボード操作を有効にしている
const sensors = useSensors(
useSensor(PointerSensor),
useSensor(KeyboardSensor, {
coordinateGetter: sortableKeyboardCoordinates,
})
);
// ドラッグ終了時の処理
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
// overがnullの場合(ドロップ先がない場合)は何もしない
if (!over) {
return;
}
// ドラッグ元とドロップ先が同じ場合は何もしない
if (active.id !== over.id) {
setItems((items) => {
// 元のインデックスと新しいインデックスを取得
const oldIndex = items.indexOf(active.id as string);
const newIndex = items.indexOf(over.id as string);
// arrayMoveユーティリティを使って配列を並び替える
return arrayMove(items, oldIndex, newIndex);
});
}
};
return (
// dnd-kitのコンテキストプロバイダー
// sensors: どの入力方法でドラッグ操作を認識するかを設定
// collisionDetection: どの要素と衝突しているかを判断するアルゴリズム
// onDragEnd: ドラッグが終了したときに呼ばれる関数
<DndContext
sensors={sensors}
collisionDetection={closestCenter}
onDragEnd={handleDragEnd}
>
{/*
SortableContext: 並び替え可能な要素のコンテキストを提供
items: 並び替え可能なアイテムのIDの配列
strategy: 並び替えの戦略(ここでは垂直方向のリスト)
*/}
<SortableContext items={items} strategy={verticalListSortingStrategy}>
<div className="w-full max-w-xs p-4 bg-gray-100 rounded">
{items.map((id) => (
// 並び替え可能な各アイテムをレンダリング
<SortableItem key={id} id={id} />
))}
</div>
</SortableContext>
</DndContext>
);
}
3.3 page.tsxに組み込み
import { SortableList } from "@/components/SortableList";
export default function Home() {
return (
<main className="flex min-h-screen flex-col items-center justify-center p-24">
<h1 className="text-4xl font-bold mb-8">dnd-kit サンプル</h1>
<SortableList />
</main>
);
}
4.動作確認
# localhost:3000で動作確認
npm run dev
5.まとめ
dnd-kitを使うとTrelloのようなカンバンや
タスク管理、ファイルアップロードなど色々と使えるケースがあるので、
使うケースが発生したら都度動作確認しながら理解を深めていきたいです。

