拖拽三步链路:拖起来(设draggable="true"并setdata)、能放上(dragover中preventdefault)、放完有结果(drop中getdata并执行业务逻辑),缺一不可。

要让拖拽真正“走通”,关键不是堆事件,而是理清三步链路:拖起来、能放上、放完有结果。缺任何一环,用户都会卡在“拖不动”或“放不进”上。
第一步:让元素可拖——从 dragstart 开始设数据
只有带 draggable="true" 的元素才能触发 dragstart;普通 div、span 默认不可拖,必须显式开启。
在 dragstart 中必须做两件事:
- 调用 e.dataTransfer.setData('text/plain', '唯一标识'),比如元素 ID 或索引,否则 drop 时读不到来源信息
- 可选设置 e.dataTransfer.effectAllowed = 'move',提示浏览器只允许移动(不影响功能,但增强语义)
注意:图片、链接默认可拖,但它们的 dragstart 不会自动携带 src/href,如需传递仍要手动 setData。
第二步:让目标区“接得住”——dragover 是核心开关
drop 事件根本不会触发,除非它的父容器在 dragover 阶段调用了 e.preventDefault()。这是 90% 失败案例的根源。
正确做法是:
- 给目标容器(如
<div id="list">)监听 <strong>dragover</strong>,并立即 <strong>e.preventDefault()</strong> <li>可配合 <strong>dragenter/dragleave</strong> 添加 hover 样式,提升反馈感,但视觉变化建议用 class 控制,而非直接操作 DOM</li> <li>不需要给目标容器设 draggable,它只是“接收方”</li> <h3>第三步:在 drop 中落地逻辑——读数据、改结构</h3> <p>drop 触发后,浏览器已准备就绪,此时要做的是业务动作:</p> <ul> <li>先调 <strong>e.preventDefault()</strong>,防止默认行为(如打开图片链接)</li> <li>用 <strong>e.dataTransfer.getData('text/plain')</strong> 取出 dragstart 存的数据</li> <li>根据场景执行对应操作:移动节点(<code>appendChild)、插入到某位置(insertBefore)、更新数组再重渲染,或发起接口请求
如果涉及排序,还需在 dragover 中结合 getBoundingClientRect() 计算鼠标相对项的位置,动态确定插入点,避免靠 event.target 判定导致跳变。
补充:别漏掉 dragend 和状态清理
dragend 在拖拽结束时必定触发(无论是否成功 drop),适合做收尾工作:
- 移除临时样式类(如拖拽中加的 opacity 或 outline)
- 重置内部状态(如 draggedIndex = null)
- 恢复被拖元素的 pointer-events(如果之前禁用了)
不处理 dragend,容易导致 UI 残留或后续拖拽异常。











