draggable="true"仅标记拖拽起点且不自动生效,必须配合事件链、setdata()调用及dragover.preventdefault()等完整流程才能实现真正拖放。

draggable="true"本身不产生拖拽效果,只改光标样式;要让元素真能拖、能放、能传数据,必须配齐事件链和 setData() 调用,漏掉任一环都会“看起来能拖,松手就失效”。
draggable="true" 必须写对且写在正确位置
这个属性不是开关按钮,而是浏览器识别拖拽起点的标记。常见错误包括:
- 写成
draggable="auto"或空字符串draggable="":Chrome/Firefox 行为不一致,建议严格写draggable="true" - 加在父容器上,比如
<div class="list"><div draggable="true">item</div></div>—— 但把draggable="true"错误地写在<div class="list"> 上,子元素不会继承 <li>硬塞给 <code><input>或<textarea></textarea>:浏览器强制禁用,设了也白设 - 元素没尺寸:空
<div draggable="true"></div>没width/height/padding,dragstart 根本不触发 - type 参数必须是标准 MIME 类型:
'text/plain'最稳,'Text'或'mydata'在 Firefox 中静默失败 - 哪怕只传 ID,也要显式写:
e.dataTransfer.setData('text/plain', e.target.id),不能省略 - 可传结构化数据,但得先
JSON.stringify()再塞进去,比如:e.dataTransfer.setData('text/plain', JSON.stringify({id: 't1', type: 'task'})) - 顺手设
e.dataTransfer.effectAllowed = 'move',让光标提示更准确(尤其 Safari) - 只监听
drop没用,dragover必须存在,且回调里必须写e.preventDefault()——e.stopPropagation()或return false都不行 - 目标容器要是空
<div id="dropzone"></div>,记得加min-height: 24px或padding: 4px,否则无法命中 - 别忽略
user-select: none或pointer-events: none:它们会直接拦住 dragstart,连光标都不变 - Safari 对
effectAllowed和dropEffect匹配更敏感,建议在dragover里补一句:e.dataTransfer.dropEffect = 'move' - 不要拿桌面端代码直接跑在手机上测试——控制台安静得可怕,事件就是不触发
- 检测
'ontouchstart' in window后必须降级:用touchstart/touchmove+transform手动模拟,或引入interact.js这类已做兼容处理的库 - 跨 iframe 或跨窗口拖拽同样受限:同域 iframe 在 Safari 中 drop 也可能被拦截,
dataTransfer数据为空是常态
dragstart 里必须调用 e.dataTransfer.setData()
没有这一步,整个拖拽链在起点就断了。drop 时 e.dataTransfer.getData('text/plain') 一定返回空字符串。
drop 不触发?八成是 dragover 没 preventDefault()
浏览器默认禁止投放,dragover 是唯一能解除该限制的事件,且 e.preventDefault() 是硬性要求。
移动端原生 drag/drop 基本不可用
iOS Safari 和 Android Chrome 均不触发 dragstart、drop 等事件,这不是代码问题,是平台级缺失。
最常被忽略的点是:拖拽不是“设个属性+绑个事件”就完事,而是一条强依赖顺序、类型匹配和 DOM 可交互性的链路。从 draggable="true" 开始,到 setData()、dragover.preventDefault()、再到 drop 里取值,任意一环出错,用户看到的就是“拖不动”或“放不下”。











