仅设置 draggable="true" 不足以实现完整拖拽功能,还需在 dragstart 中调用 setdata() 传递数据、在 dragover 中 preventdefault() 开启投放许可、在 drop 中 preventdefault() 并手动处理 dom 移动。

直接用 draggable="true" 不足以让元素真正可拖拽——它只是“允许拖拽”的开关,后续必须配合事件监听和数据传递逻辑,否则拖不动、放不下、没反馈。
draggable 属性本身只控制是否启用拖拽入口
draggable 是布尔属性,设为 true 才能让非默认可拖元素(如 <div>、<code><span></span>)进入拖拽流程。但注意:
-
<img>和<a></a>默认就是draggable="true",不写也行,但显式写出更可控 -
draggable="false"会禁用拖拽,哪怕它原本是可拖的(比如图片) -
draggable="auto"由浏览器决定,行为不一致,别用 - 仅设置
draggable="true"后,鼠标悬停时不会变抓手光标,需手动加 CSS:cursor: move
dragstart 里必须调用 setData(),否则 drop 拿不到数据
很多初学者以为只要拖过去就能自动移动 DOM,其实不是。dataTransfer.setData() 是唯一能跨事件传递信息的机制,drop 事件里全靠它取 ID 或内容:
- 必须在
dragstart中调用,且至少传一个 MIME 类型,常用"text/plain"或"application/json" - 不要只传
innerHTML,容易 XSS;推荐传id或结构化对象字符串:event.dataTransfer.setData("application/json", JSON.stringify({ type: "card", id: "item-123" })) - 如果传空字符串或未调用
setData(),getData()返回空,drop逻辑就失效
dragover 必须 preventDefault(),否则 drop 根本不触发
这是最常踩的坑:写了 drop 监听,但死活不执行。根本原因是浏览器默认禁止任何元素接收拖放,dragover 的 preventDefault() 就是“开门许可”:
- 只在目标容器上监听
dragover,并立即调用event.preventDefault() - 别在父级或 body 上乱加,否则可能干扰其他区域
- 可以顺便设
event.dataTransfer.dropEffect = "move",但不是必需项 - 如果目标区域是空 div,记得给它设个最小尺寸(如
min-height: 40px),否则拖进去时 hover 区域不可见
drop 事件里要手动处理 DOM 移动,不能依赖浏览器自动搬运
drop 只是通知“你松手了”,具体怎么放、放哪儿、要不要克隆,全由你代码决定:
- 必须先
event.preventDefault(),否则页面可能跳转或打开文件(尤其拖入链接/图片时) - 用
getData()拿到dragstart里存的数据,再通过document.getElementById()或状态管理定位源元素 - 移动 DOM:用
appendChild()或insertBefore();复制 DOM:用cloneNode(true) - 如果拖拽源和目标在不同父容器下,注意
appendChild()会自动从原位置移除节点——这是预期行为,不是 bug
真正难的不是写通基础流程,而是处理嵌套容器冲突、移动端 touch 兼容、视觉反馈节奏、以及拖拽中途取消时的状态回滚——这些没被 draggable 属性覆盖,得自己兜底。











