拖拽失败主因是dragstart未调用setData(),需设dataTransfer并指定effectAllowed;dragover必须preventDefault以开启drop许可;drop时应按鼠标位置用before/after插入而非insertBefore。

dragstart里不设dataTransfer,拖拽根本不会开始
加了 draggable="true" 却拖不动,不是浏览器bug,是拖拽流程被卡在第一步。浏览器默认只对图片、链接、选中文本启用完整拖放链,<tr> 被视为普通容器,dragstart 触发后若没调用 setData(),整个流程就静默终止。
必须在 dragstart 中至少写一行:e.dataTransfer.setData('text/plain', String(rowIndex)) 或更稳妥的 e.dataTransfer.setData('text/plain', row.id)。Safari 还要求不能传空字符串,哪怕只是 '0' 也得有。
- 别传 DOM 节点或对象——
setData()只接受字符串 - 优先用
id而非索引,重排过程中索引会变,id更可靠 - 别漏掉
e.dataTransfer.effectAllowed = 'move',否则部分浏览器光标显示异常
dragover不preventDefault,drop事件永远不会触发
dragover 是拖拽过程中的“许可闸门”。不调 e.preventDefault(),浏览器就当你要下载文件或跳转链接,直接拦截 drop,控制台也不会报错,只会看到鼠标变成圆圈斜杠。
这个 preventDefault() 必须写在 dragover 回调里,而且要针对目标 <tr>(或其父 <tbody>)监听,不能只绑在 <table> 上——否则拖到空白处就失效。
立即学习“前端免费学习笔记(深入)”;
- 只在
dragover里阻止还不够,drop事件里也要再写一次e.preventDefault() - 别给
<table>或<tbody>加draggable="true",否则整块被拖,失去行级控制 - 如果表格用了
border-collapse: collapse,插入线样式别用outline,改用border-top或border-bottom
drop时用before()/after(),别用insertBefore()搞错参照节点
常见错误是:拿到目标 <tr> 后,直接 target.parentNode.insertBefore(draggedTr, target),结果把行插到目标上方——但用户明明拖到了目标下方。问题出在没判断鼠标相对位置。
正确做法是在 dragover 中实时计算:const rect = target.getBoundingClientRect(); const isTopHalf = e.clientY ,然后在 <code>drop 里根据标记执行 target.before(draggedTr) 或 target.after(draggedTr)。
- IE11 不支持
before()/after(),需降级为parentNode.insertBefore(draggedTr, isTopHalf ? target : target.nextSibling) - 绝对不要重写
tbody.innerHTML——所有<input>的值和事件监听器全丢 - 拖完立刻调用
tbody.dispatchEvent(new Event('rowsorted', {bubbles: true})),方便外部同步数据
移动端原生 drag/drop 基本不可用,别白费功夫
iOS Safari(包括 iPadOS)从 15.4 版起才部分支持 draggable,且对非 <img> 元素行为极不稳定:拖拽卡顿、drop 不触发、与 touch 事件冲突是常态。这不是你代码的问题,是浏览器限制。
真实项目中,要么用 SortableJS 这类已处理兼容性的库,要么自己用 touchstart → touchmove → touchend 模拟,手动节流、禁用默认滚动、加 transform: translateY() 实现视觉跟随。
- 别指望
-webkit-user-drag: element解决问题,它只影响光标,不修复事件链 -
touchmove频繁触发,不做requestAnimationFrame节流会卡顿 - 拖拽结束时,JS 数据数组顺序必须手动更新——DOM 排了,数组没动,提交就是错序
input 值不丢、事件不掉、索引不错位,每一步都得亲手校验。



















