原生HTML drag/drop列表排序必须监听dragover并调用preventDefault()才能触发drop事件;需在dragstart中setData()设占位数据,用closest()定位目标元素,动态计算插入点,否则位置错乱、Safari失效。
原生 HTML 的 drag / drop 事件做列表排序,表面简单,实际极易出错——拖拽后位置错乱、跨容器失效、多次触发、不兼容 Safari,这些问题不是“加个 draggable="true" 就能解决的。
ondragstart + ondrop 会丢序?浏览器默认只在拖拽结束时触发 drop,但你根本不知道用户想把元素插到哪个位置。没有“悬停时实时计算插入点”的逻辑,drop 事件拿到的 target 往往是父容器本身,而不是目标 li 或 div。
dragover 并调用 event.preventDefault(),否则 drop 不会触发(这是最常被忽略的一步)dragenter 和 dragover 会高频触发,不能在里面直接操作 DOM,否则卡顿或重复插入dataTransfer.items 支持极弱,别依赖它传自定义数据;改用 dataTransfer.setData("text/plain", id)
dragend 后 DOM 没更新?检查 event.target 和实际移动节点是否一致很多人在 dragend 里重新渲染列表,但此时 event.target 是被拖的原始节点,而它可能已被移出文档流——直接操作它会失败。真正该操作的是“目标位置的参考节点”。
dragstart 中缓存被拖节点的 id 或索引:event.dataTransfer.setData("text/plain", event.target.dataset.id)
drop 事件中,用 event.target 判断插入位置:如果是目标项(如 li),就插到它前面;如果是容器,就 append 到末尾insertBefore 直接操作 event.target,先用 event.target.closest("li") 确认有效目标Safari 会阻止非表单元素的 drop,且对 dataTransfer.effectAllowed 更敏感;Chrome 则默认允许但不校验。统一做法是显式声明并拦截默认行为。
立即学习“前端免费学习笔记(深入)”;
dragstart 中设:event.dataTransfer.effectAllowed = "move"
dragenter、dragover、dragleave 处理函数都必须有 event.preventDefault()
drop 中使用 event.dataTransfer.files 或 items,只用 getData("text/plain") 取 IDdraggable="true",同时 CSS 加 user-select: none 防止文字选中干扰真正的难点不在拖拽本身,而在于“悬停时视觉反馈”和“松手瞬间精准插入”之间的同步——DOM 重排时机、事件冒泡顺序、移动端 touch 与 mouse 事件混用,都会让排序错一位。上线前务必在 iOS Safari 和 macOS Safari 下实测三遍。