Zenn
Closed5

dnd-kitでReactアプリにドラッグ&ドロップを実装する

js4000alljs4000all

ドラッグ可能なコンポーネントは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 })
js4000alljs4000all

ドロップ先となるコンポーネントは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}` })
js4000alljs4000all

ドラッグ終了イベントのハンドラを実装する。
イベントオブジェクトのactiveがドラッグ対象(useDraggableに指定したオブジェクト)、overがドロップ対象(useDroppableに指定したオブジェクト)。ドロップ可能コンポーネント上でドラッグが終了しなかった場合は、overnullになる。

  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);
    ...
  };
js4000alljs4000all

DndContextコンポーネント内に配置する。ドラッグ終了イベントのハンドラは、DndContextonDragEndに指定する。

      <DndContext onDragEnd={handleDragEnd}>
        ...
            {tags.map((tag) => (
              <DraggableTag key={tag} tag={tag} />
            ))}
         ...
            {notes.map((note, idx) => (
              <DroppableNote
                key={idx}
                note={note}
                index={idx}
              />
            ))}
        ...
      </DndContext>
このスクラップは2日前にクローズされました
ログインするとコメントできます