draggable属性仅是拖拽开关,需JavaScript补全逻辑:dragstart中必须setData并preventDefault,drop前需在dragover中preventDefault且确保容器有高度,移动端应避免依赖原生拖拽。

draggable 属性本身不能单独实现拖拽行为,它只是个“允许拖拽”的开关,不带逻辑、不传数据、不触发视觉反馈——所有实际功能必须靠 JavaScript 补全。
draggable="true" 为什么设了却没反应?
- 鼠标光标变拖拽样式 ✅,但松手后什么都没发生 ❌
-
drop事件压根不触发 ❌ -
dataTransfer.getData()拿到空字符串 ❌
常见原因有这些:
-
dragover事件里没调e.preventDefault(),浏览器默认禁止投放 -
dragstart里漏掉e.dataTransfer.setData('text/plain', ...),drop 时身份信息丢失 - 目标容器是空的(比如
ul里没li),高度塌陷,dragover根本不冒泡进来 - 给
input、textarea或禁用状态元素硬加draggable="true",浏览器直接无视
别指望属性自己干活。它只负责让 dragstart 有机会触发。
dragstart 必须手动塞数据,否则拖了个寂寞
draggable="true" 不自动告诉目标“我拖的是谁”。你得自己存标识:
立即学习“前端免费学习笔记(深入)”;
- 类型必须用
'text/plain',兼容性最稳;'text/html'在旧 WebView 里静默失败 - 值推荐用
dataset.id或id,别用索引(列表重排后就错) - 别传对象:
setData()只接受字符串,JSON 要JSON.stringify()再塞 - 加一句
e.dataTransfer.effectAllowed = 'move',避免光标显示为复制图标
示例:
element.addEventListener('dragstart', e => {
e.dataTransfer.setData('text/plain', e.target.dataset.itemId);
e.dataTransfer.effectAllowed = 'move';
});
drop 事件进不来?先查 dragover 和容器高度
drop 是个“贵族事件”:它只在 dragover 被允许的前提下才可能触发,而且目标容器得能被鼠标真正“悬停到”。
-
dragover回调开头必须写e.preventDefault(),缺一不可 - 容器若为空(比如刚初始化的
ul),要加min-height: 1px或伪元素撑开,否则没有 hit area - 别只监听
li,得在父容器(如ul)上监听dragover和drop,否则拖到空白处就失效 -
drop回调里第一句也得是e.preventDefault(),位置不能晚于 DOM 操作
插入逻辑容易翻车的地方:
- 用
e.target.closest('li')判断落点,别直接用e.target - 算中线判断上下半区:
(rect.top + rect.bottom) / 2,不然鼠标偏一点就插错边 - 插入前检查
dragged === targetLi,避免insertBefore(dragged, dragged)报错
移动端和低版本浏览器根本别信原生 draggable
iOS Safari 对原生拖拽支持极差:拖动卡顿、dragstart 偶发不触发、drop 事件丢失——这不是你代码问题,是 WebKit 的限制。
- 不要在生产环境依赖它做移动端排序或画布操作
- 替代方案:用
SortableJS、react-dnd或手写touchstart/move/end逻辑 - 如果必须降级,至少把拖拽入口做成按钮触发“编辑模式”,再切到自由拖动态,别让用户无感失败
真正难的不是写通逻辑,而是把缩放、滚动、嵌套 iframe、transform、overflow hidden 这些边界条件全兜住——辅助线漂移、drop 区域消失、坐标算错,90% 都出在这里。



















