Closed5
dnd-kitでReactアプリにドラッグ&ドロップを実装する
2025/03時点での主流はdnd-kit
らしい。
npm install @dnd-kit/core
以下の3つを実装する。
- ドラッグ対象となるコンポーネント
- ドロップ先となるコンポーネント
- ドラッグ終了イベントのハンドラ
最後にDndContext
コンポーネント内に配置すれば完成。
ドラッグ可能なコンポーネントはuseDraggable
で実装する。
戻り値のattributes
listeners
setNodeRef
をドラッグ可能にしたい要素に対して適用するだけ。
function DraggableTag({ tag }: DraggableTagProps) {
const { attributes, listeners, setNodeRef, transform } = useDraggable({ id: tag });
const style = {
transform: transform ? `translate(${transform?.x}px, ${transform?.y}px)` : undefined,
};
return (
<div
ref={setNodeRef}
{...listeners}
{...attributes}
style={style}
className="px-2 py-1 bg-blue-100 rounded border cursor-move flex justify-between items-center"
>
<span>{tag}</span>
</div>
);
}
4つ目の戻り値であるtransform
を元にCSSのtransform
プロパティ(translate
操作)を作り、要素に指定するのが地味にミソ。これをやると、対象要素がドラッグ操作に追従して動くようになる。
const style = {
transform: transform ? `translate(${transform?.x}px, ${transform?.y}px)` : undefined,
};
useDraggable
の引数に指定したオブジェクトは、ドラッグ終了イベントのハンドラ内で受け取れる。
useDraggable({ id: tag })
ドロップ先となるコンポーネントはuseDroppable
で実装する。
戻り値のsetNodeRef
をドロップ先としたい要素に付与するだけ。
function DroppableNote({ note, index }: DroppableNoteProps) {
const { isOver, setNodeRef } = useDroppable({ id: `note-${index}` });
return (
<div
ref={setNodeRef}
className={`p-3 border rounded bg-white flex flex-col gap-1 transition ${
isOver ? 'border-blue-500 shadow-md' : 'border-gray-300'
}`}
>
<div className="font-semibold">{note.text}</div>
<div className="text-xs text-gray-500 bg-gray-100 px-2 py-0.5 rounded w-fit">
{note.tag || '(no tag)'}
</div>
</div>
);
}
isOver
の値は、対象要素上にドラッグ中の要素が重なっているかどうかで変化する。スタイルの切り替え等に使える。
className={`p-3 border rounded bg-white flex flex-col gap-1 transition ${
isOver ? 'border-blue-500 shadow-md' : 'border-gray-300'
}`}
useDroppable
に指定したオブジェクトは、ドラッグ終了イベントのハンドラ内で受け取れる。
useDroppable({ id: `note-${index}` })
ドラッグ終了イベントのハンドラを実装する。
イベントオブジェクトのactive
がドラッグ対象(useDraggable
に指定したオブジェクト)、over
がドロップ対象(useDroppable
に指定したオブジェクト)。ドロップ可能コンポーネント上でドラッグが終了しなかった場合は、over
はnull
になる。
const handleDragEnd = (event: DragEndEvent) => {
const { active, over } = event;
if (!over || !active) return;
const tag = String(active.id);
const noteIndex = parseInt(over.id.replace('note-', ''), 10);
...
};
DndContext
コンポーネント内に配置する。ドラッグ終了イベントのハンドラは、DndContext
のonDragEnd
に指定する。
<DndContext onDragEnd={handleDragEnd}>
...
{tags.map((tag) => (
<DraggableTag key={tag} tag={tag} />
))}
...
{notes.map((note, idx) => (
<DroppableNote
key={idx}
note={note}
index={idx}
/>
))}
...
</DndContext>
このスクラップは2日前にクローズされました
ログインするとコメントできます