Skip to content
返回设计中心

拖拽排序

带键盘对等、放置指示、乐观持久化的指针重排序。

组件模式高级

为什么

拖拽是按空间直觉重排项的最快方式。但仅指针 DnD 排除键盘和触摸用户且易出错(卡顿、无放置提示)。指针+键盘+触摸方案服务所有人。

何时使用

用于列表内重排、看板列或树节点。避免 >50 项的跨容器移动 — 认知负荷胜过移至下拉。

如何做

1. 选库 — @dnd-kit/core(无障碍,推荐)或 react-beautiful-dnd(旧) 2. 指针 — onPointerDown 捕获项,拖拽覆盖跟随光标 3. 放置指示 — 2px 线显示项将落处,由指针在目标上计算 4. 键盘 — Space 拾起、箭头键移动、Space 放下;通过 aria-live 播报 5. 触摸 — 长按启动(避免与滚动冲突),或专用拖拽手柄 6. 乐观 — 立即重排本地状态,后台 PATCH 服务器,出错回滚 7. 约束 — 列表锁定一轴、限制放置区、看板吸附栅格

示例

import { DndContext, useDraggable, useDroppable, PointerSensor, KeyboardSensor } from "@dnd-kit/core"; function SortableItem({ id, label }: { id: string; label: string }) { const { attributes, listeners, setNodeRef, transform } = useDraggable({ id }); const style = transform ? { transform: `translate(${transform.x}px,${transform.y}px)` } : undefined; return ( <li ref={setNodeRef} style={style} {...listeners} {...attributes} className="cursor-grab focus:ring-2" tabIndex={0}> {label} <span className="sr-only">按 Space 拖拽,箭头移动</span> </li> ); } function SortableList({ items, onReorder }: Props) { return ( <DndContext sensors={[useSensor(PointerSensor), useSensor(KeyboardSensor)]} onDragEnd={(e) => onReorder(e.active.id, e.over.id)}> <ul role="list"> {items.map((it) => <SortableItem key={it.id} id={it.id} label={it.label} />)} </ul> </DndContext> ); }

常见问题

  • ⚠仅指针 — 键盘用户无法重排,未过 WCAG 2.1.1 键盘可操作
  • ⚠无放置指示 — 用户盲放,项落在意外处
  • ⚠拖拽与滚动冲突 — 触摸用户无法滚动列表,每个手势启动拖拽

最佳实践

  • ✓始终含 KeyboardSensor — Space 拾起、箭头移动、Space 放下
  • ✓显示放置指示线 — 用户释放前需看到项将落处
  • ✓触摸用拖拽手柄 — 长按手柄拖拽,其余可自由滚动

常见问题解答