拖拽最小单元是或,需设draggable="true"且有尺寸;dragover必须preventdefault()才能触发drop;位置用data-*属性记录而非dom索引;移动端需touch事件降级实现。

拖拽目标必须是 <td> 或其直接子元素,不能只监听 <code><table><p>浏览器原生 <code>dragstart 事件不会冒泡到 <table>,如果把 <code>draggable="true" 加在表格或 <tr> 上,基本拖不动。实际生效的最小可拖拽单元是 <code><td>(或 <code><th>),且需确保它有明确尺寸和非 <code>display: none 状态。
常见错误:给整个 <table> 设 <code>draggable="true",结果拖拽时鼠标图标没变、dragstart 根本不触发。正确做法是:
- 每个可拖动卡片/项用
<td> 包裹,设 <code>draggable="true" - 若内容是自定义组件(如
<div class="card">),必须加 <code>tabindex="0"并监听keydown模拟键盘拖拽支持(无障碍必需) <td> 不要设 <code>overflow: hidden,否则拖拽反馈图可能被裁剪dragover必须阻止默认行为,否则drop不会触发这是最常踩的坑:光写
addEventListener('dragover', ...)不够,不调event.preventDefault(),浏览器就当“不允许投放”,drop事件压根不会来。注意点:
- 只对目标
<td>(即想让卡片落进去的格子)绑定 <code>dragover和drop -
dragover里只需event.preventDefault(),别做 DOM 更新——太频繁会卡顿 - 如果需要视觉反馈(比如高亮目标格),改
td的class或style.boxShadow,别重绘整张表 - 初始化时,给每个
<td> 写 <code>data-column="todo"、data-row="1"这类语义化属性 - 拖拽开始时,在
dragstart里用event.dataTransfer.setData('text/plain', JSON.stringify({id: 'task-123', column: 'in-progress'})) -
drop触发后,读目标<td> 的 <code>dataset.column,再调 API 更新后端状态,而不是移动 DOM 节点移动端不支持原生 drag API,得降级用
touchstart/move/endiOS Safari 和部分安卓 WebView 完全忽略
draggable和 drag 事件。必须单独实现触摸拖拽逻辑。关键差异:
- 没有
dataTransfer,需用全局变量暂存拖拽项 ID 和起始坐标 - 监听
touchmove时手动计算位移,用transform: translate()模拟拖拽跟随效果 - 投放判定靠
touchend时的document.elementFromPoint()找到目标<td>,再取其 <code>dataset - 务必加
touch-action: none到拖拽元素上,否则系统手势(如滑动页面)会抢事件
真正麻烦的不是拖拽本身,而是保持桌面端和移动端状态同步——比如一个任务从「todo」列拖到「done」列,两边都要立刻反映新位置,且不能因网络延迟出现双写冲突。
- 没有
用
data-*属性记录行列位置,别依赖 DOM 索引rowIndex/cellIndex在动态增删行或合并单元格(colspan/rowspan)时完全不可靠。真实项目里,格子位置应由业务数据驱动。推荐方式:
- 只对目标











