HTML5拖放API需正确设置dataTransfer.setData()传递字符串标识,drop事件必须preventDefault且监听容器需有高度;移动端原生drag不支持,应改用SortableJS等库或降级交互。

dragstart里不设dataTransfer,拖拽就失效
浏览器根本不认你拖的是谁——draggable="true"只是开关,真正传递身份信息靠的是dataTransfer.setData()。漏掉这一步,drop事件里getData()永远拿空字符串,后续所有逻辑都断在起点。
常见错误写法:element.addEventListener('dragstart', e => {}),里面什么都没干;或者只写了e.dataTransfer.setData('text/html', ...),但旧版 Edge 和部分安卓 WebView 不支持text/html,直接静默失败。
- 必须用
text/plain类型,兼容性最稳 - 传
id比传索引更可靠——列表重排后索引会变,data-id不会 - 别传对象或 DOM 节点:
setData()只接受字符串,JSON.stringify也得再转成字符串 - 加一句
e.dataTransfer.effectAllowed = 'move',明确告诉浏览器这是移动操作,避免误触发复制行为
drop事件里不调preventDefault,根本进不来
drop事件默认被浏览器拦截成“下载”或“跳转”,不手动拦,它连执行机会都没有。很多人只在dragover里写了e.preventDefault(),结果drop回调压根不触发——两个事件独立,缺一不可。
更隐蔽的问题是:监听对象错了。如果只给ul绑drop,但用户拖到li之间的空白处(比如最后一条后面),e.target可能是ul本身,而closest('li')返回null,插入逻辑就崩了。
立即学习“前端免费学习笔记(深入)”;
-
drop和dragover都要在同一个容器上监听,通常是ul或ol - 容器必须有高度,空列表会塌陷,拖进去没响应——加
min-height: 1px或伪元素撑开 -
e.preventDefault()必须出现在drop回调开头,位置不能错 - 用
e.target.closest('li')而不是直接e.target,防止点到内部文字、图标时拿错节点
insertBefore逻辑写错,顺序立刻翻车
DOM 移动不是“删了再加”,而是直接挪位置。insertBefore的第二个参数决定插在哪:传targetLi.nextSibling是插到目标后面,传targetLi是插到前面。混用或漏判方向,会导致元素跳到错误位置,甚至自己插自己引发异常。
判断上下半区是关键——光看e.clientY和targetLi.getBoundingClientRect().top不够,得算中线:(rect.top + rect.bottom) / 2。否则鼠标稍微偏一点,就误判成“插到前面”,实际想放后面。
- 插入前必须检查
dragged === targetLi,否则insertBefore(dragged, dragged)会报 DOM 异常 - 移动端无法触发原生
dragstart,iOS Safari 默认禁用,安卓 WebView 多数也关着——真机调试时发现完全没反应,不是代码问题,是平台限制 - 不要依赖
dragend清理状态,它在移动端可能不触发;改用drop或dragleave做收尾
移动端必须绕过原生限制,不能只写一套逻辑
HTML5 原生拖放 API 在移动浏览器里基本等于摆设:draggable="true"属性无效,dragstart不触发,drop永远不会来。这不是 bug,是规范层面的设计——触摸交互和鼠标交互模型不同,浏览器干脆禁掉了。
硬加touchstart模拟dragstart只能骗过部分安卓 Chrome,iOS 完全不买账。强行搞兼容,代码膨胀、手势冲突、滚动卡顿接踵而至。
- 生产环境建议直接切方案:用
SortableJS这类成熟库,它内部已处理 touch/mouse 双路径 - 若坚持原生,至少把移动端降级为“点击排序”或“长按弹出菜单调整顺序”,别强拖
- 测试必须真机跑,模拟器里的“移动端模式”对 drag/drop 的模拟极其有限,信它等于信假消息
原生 API 看似简单,但边界条件多、平台差异大、移动端几乎不可靠——真正上线前,得把 iOS 和主流安卓机型各测一遍,光看桌面端控制台输出毫无意义。



















