draggable属性仅控制是否允许抓取,不实现拖拽功能;必须同时满足draggable="true"、dragstart中调用datatransfer.setdata()、目标元素dragover中持续调用preventdefault()三者缺一不可。

draggable 属性本身不实现拖拽功能,只控制“是否允许被抓取”。设了 draggable="true" 却没反应,基本是因为漏掉了事件链中某一个强制环节。
dragstart 事件里必须调用 dataTransfer.setData()
这是拖拽启动的硬性门槛:浏览器检测到 draggable="true" 后,只有在 dragstart 回调中成功写入至少一个数据项,才会真正进入拖拽流程。否则光标可能变样式,但后续事件全不触发。
-
dataTransfer.setData('text/plain', 'some-id')是最简可用写法;传空字符串''也行,但不能省略这行 - 避免用非标准类型如
"Text"(首字母大写),Chrome/Firefox 会静默忽略 - 若要传结构化数据,推荐
dataTransfer.setData('application/json', JSON.stringify({id: 1, type: 'field'})) - 不能在
dragstart外提前设置——dataTransfer对象只在该事件上下文中可写
目标元素的 dragover 必须每次调 e.preventDefault()
drop 事件能否触发,完全取决于 dragover 是否被阻止默认行为。这不是“拦一次就行”的逻辑,而是每次鼠标移动经过目标区域时都得调用 preventDefault(),否则浏览器始终认为“此处不可投放”。
- 只在
dragenter里preventDefault()是无效的;dragleave更不需要拦 - 目标如果是空
<div>,需确保它有尺寸(比如 <code>min-height: 20px或内边距),否则dragover根本不会派发 - Safari 对
effectAllowed和dropEffect匹配更严格,建议在dragstart中加e.dataTransfer.effectAllowed = 'move',在dragover中加e.dataTransfer.dropEffect = 'move' - 正确做法是把控件包进
<div> 或 <code><li>等容器,并对容器设draggable="true" - 拖拽时用
cloneNode(true)创建副本,再清空新节点的id和draggable属性,避免重复 ID 和二次拖拽干扰 - 别依赖
ondragstart内联写法,用addEventListener('dragstart', ...)更可控 - 开发者工具里的“设备模拟”模式会误导你——它只是模拟视口,不模拟真实触摸事件流
- 真实项目中只要涉及手指拖拽,必须换方案:用
touchstart/touchmove+CSS transform手动模拟,或引入interact.js这类封装好的库 - 如果产品需覆盖 App 内嵌 H5 场景,
draggable不应作为核心交互路径设计
表单控件(<input>、<textarea></textarea>)不能直接设 draggable="true"
几乎所有主流浏览器(Chrome、Firefox、Safari)都会无视对原生表单控件显式设置的 draggable="true"。这不是 bug,是渲染层限制——它们不支持 setDragImage 定位,也无法稳定响应拖拽事件。
移动端根本不存在原生 draggable 支持
iOS Safari、Android Chrome、所有 WebView 场景下,draggable 属性和整套 Drag & Drop API 均不触发。这不是兼容性补丁能解决的问题,是规范层面未定义触摸拖拽行为。
draggable 是个开关,不是功能模块。三个动作缺一不可:draggable="true"、dragstart 中 setData()、目标上持续 dragover.preventDefault()。漏掉任意一个,整个链路就断在看不见的地方。











