dragover事件必须调用preventDefault()才能触发drop事件,否则浏览器默认禁止投放;目标元素需有宽高且可见,dragover须绑定并每次执行preventDefault(),跨iframe时dataTransfer内容会被清空。

dragover事件不调preventDefault(),drop根本不会触发
浏览器在拖拽进入目标区域时,默认行为是“禁止投放”,dragover事件只是通知你“拖过来了”,但不阻止默认行为,drop事件压根不会派发——不是监听漏了,是流程被拦在半路。
必须在dragover回调里写e.preventDefault(),e.stopPropagation()或return false都不行,只有preventDefault()能解锁投放通道。
- 目标容器是空
<div>?确认它有宽高(比如min-height: 20px),否则无法命中 - 目标容器含子元素?
dragover会冒泡,建议统一绑定在父容器上,避免重复调用preventDefault() - 用了
dropzone属性?别信它——该属性早已废弃,现代浏览器完全无视
draggable="true"和dragover必须配对出现,缺一不可
draggable="true"只负责让鼠标变成拖拽光标,它不触发任何事件,也不影响目标区逻辑。真正把“可拖”和“可放”串起来的,是dragstart + dragover + drop三者协作。
常见错误:只给源元素加draggable="true",目标区只绑drop,结果拖到上面没反应——因为dragover没监听,也没preventDefault(),浏览器直接跳过投放判断。
立即学习“前端免费学习笔记(深入)”;
-
draggable="true"必须写在被拖元素上,<input>、<textarea>加了也无效 - 目标区不需要
draggable="true",但必须显式监听dragover并调preventDefault() - 如果目标区是
display: none或visibility: hidden,dragover压根不会触发
dragover里不能只preventDefault,还得考虑插入位置计算
对于排序类场景(比如列表拖拽重排、网格拖放),仅靠drop里的event.target不够用——它可能是子文本节点、图标或空白间隙,不是你想要的插入锚点。
真实落位得靠dragover阶段动态算:用e.clientY对比目标项的getBoundingClientRect().top和bottom,决定插在上方还是下方。
- 别在
drop里才算位置,用户拖动过程中就得实时反馈,否则体验卡顿 - 计算时注意滚动容器偏移,
getBoundingClientRect()返回的是视口坐标,需结合window.scrollY校正(若容器非body) - 移动端没有
dragover事件,这套逻辑必须降级为touchmove+ 手动坐标比对
跨iframe拖放时dragover仍要preventDefault,但dataTransfer会清空
从主页面拖进<iframe>,或者Electron里拖进<webview>,dragover事件照常触发,preventDefault()也必须调——否则drop依旧不触发。
但此时event.dataTransfer.getData('text/plain')大概率返回空字符串,因为跨上下文时浏览器强制清空dataTransfer内容,只保留基础类型(如"text/plain"),自定义数据全丢。
- 解决方案:拖拽开始前,把关键ID存到
sessionStorage;drop时读取,再清理 - 不要依赖
event.dataTransfer.types判断可用类型,跨iframe后它可能只剩["text/plain"] - Safari对
effectAllowed/dropEffect匹配更严格,建议dragstart里设e.dataTransfer.effectAllowed = 'move',dragover里设e.dataTransfer.dropEffect = 'move'
dragover必须在每次触发时都调preventDefault()——不是只绑一次,而是每次事件来都要执行。很多人写成条件判断后才调,结果一半时间drop静默失败。



















