UGA Boxxx

つぶやきの延長のつもりで、知ったこと思ったこと書いてます

【dnd-kit】input や button を避けてドラッグ開始条件を制御する

dnd-kit を使ってドラッグ&ドロップを実装する際、フォーム要素(inputbutton など)を含む場合に注意が必要だった

ドラッグ操作とクリック・入力操作が競合しないように制御する方法を調べたのでメモ

発生した問題

  • inputselect をクリックすると、意図せずドラッグが開始される
  • ボタンを押したいだけなのに、ドラッグ挙動が優先される
  • スマートフォン環境でタップ操作とドラッグ操作が干渉する

解決方法1: イベント伝播を止める

フォーム要素に対して、onPointerDown などのイベントでドラッグ操作への伝播を防ぐ

実装例

<input
  type="text"
  onPointerDown={(e) => e.stopPropagation()}
/>

<button onPointerDown={(e) => e.stopPropagation()}>
  ボタン
</button>

ポイント

  • ドラッグを開始させたくない要素に限定して適用する
  • マウス操作・タッチ操作の両方に対応するには onPointerDown を使うと良い

ただ、あまりに多いと全部に適用するのはつらい

方法2: activationConstraint を使う

DndContextに設定するsensor の設定の1つ activationConstraint では、ドラッグ開始条件を細かく制御できる

一定距離動かした場合のみドラッグを開始するなどの設定が可能

実装例

  const sensors = useSensors(
    useSensor(PointerSensor, {
      activationConstraint: {
          distance: 5,
      },
    })
  );

...

  <DndContext sensors={sensors}>

よく使うパターン

設定項目 内容
distance 指定距離以上動かした場合のみドラッグ開始
delay 長押しした場合のみドラッグ開始(ms単位)
tolerance タッチ操作時の誤差許容範囲

方法3: Drag Handle を導入する

ドラッグ操作を特定の領域やアイコンに限定することで、フォーム操作との干渉を避ける

listeners を全体に付けるのではなく、ドラッグ専用のハンドル部分にだけ適用する

実装例

<div ref={setNodeRef} {...attributes}>
  <span>ドラッグ対象の内容</span>
  <span {...listeners} className="drag-handle"></span>
</div>