dnd-kit では、ドラッグ中にどの要素を「ドロップ対象」とするかを判定する仕組みとして collisionDetection が用意されている
デフォルトの戦略だけでは操作感に課題が出ることも多いため、状況に応じてカスタマイズすることで最適な動作が実現できる
collisionDetection の基本
collisionDetection は、ドラッグ中に「どのドロップエリアが現在の対象か」を決定するロジック
DndContext に渡すことで動作を切り替えられる
主な戦略
| 戦略名 | 説明 |
|---|---|
closestCenter |
要素の中心点同士の距離で判定(デフォルト) |
pointerWithin |
ポインタが領域内に入っているかで判定 |
rectIntersection |
矩形の重なり具合で判定 |
closestCorners |
四隅の距離で判定 |
操作感が悪くなるケース
- 高さや幅がバラバラな要素を扱う場合、
closestCenterだと意図した判定がされない - 小さい要素にドロップしづらい
- グリッドレイアウトでは、標準戦略では違和感が出やすい
このような場合は、rectIntersection やカスタム戦略が有効になる
rectIntersection の活用
rectIntersection は、ドラッグ対象の矩形とドロップ対象の矩形がどれだけ重なっているかで判定する
要素サイズに依存せず、重なった時点でドロップ判定されるため、操作感が自然になる場面が多い
使用例
import { DndContext, rectIntersection } from '@dnd-kit/core'; <DndContext collisionDetection={rectIntersection}> ... </DndContext>
カスタム戦略の実装
標準の戦略では対応できない場合、自分で判定ロジックを定義できる
たとえば「重なり量が多い順に優先する」戦略を作成することが可能
例:カスタム rectIntersection
type Rect = { top: number; bottom: number; left: number; right: number; width: number; height: number; }; function calculateIntersectionArea(a: Rect, b: Rect) { const xOverlap = Math.max(0, Math.min(a.right, b.right) - Math.max(a.left, b.left)); const yOverlap = Math.max(0, Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top)); return xOverlap * yOverlap; } const customCollisionDetection: CollisionDetection = ({ droppableRects, collisionRect }) => { const collisions = []; for (const [id, rect] of droppableRects.entries()) { if (!rect) return; const intersectionArea = calculateIntersectionArea(collisionRect, rect); if (intersectionArea > 0) { collisions.push({ id, data: { value: intersection }, }); } } // intersection率が高い順に並べて返す return collisions.sort((a, b) => b.data.value - a.data.value); };
複数戦略の組み合わせ
状況に応じて、複数の戦略を切り替えることもできる。
例:pointerWithin を優先しつつ、fallback で closestCenter を使用
const combinedStrategy = (args) => { const pointerHits = pointerWithin(args); if (pointerHits.length > 0) return pointerHits; return closestCenter(args); };