draggable="true"仅触发拖拽事件,不自动移动DOM或更新数据;跨容器投放需在dragover中调用preventDefault();推荐用WeakMap存拖拽上下文,移动端应降级为touch事件模拟。

draggable="true"只是拖拽开关,不是跨容器搬运工
设了 draggable="true" 只会让浏览器派发 dragstart、dragover、drop 这类事件,它不移动 DOM、不更新数据数组、不跨列同步状态。看板里每张卡片都加这个属性,只代表“允许你点住拖”,不代表松手后自动插进目标列——插入位置得你算,DOM 得你手动 insertBefore(),数据索引得你重排。
dragover.preventDefault() 是跨容器投放的硬性门槛
常见错误是只监听 drop,却漏掉 dragover 里的 event.preventDefault(),结果拖进目标列时光标变红叉,松手毫无反应。这不是 bug,是浏览器默认策略:非表单/链接元素禁止投放,必须显式放行。
- 必须在目标容器(比如
.column)上监听dragover - 回调里第一句就得写
event.preventDefault(),不能只写空函数或放在条件分支里 - 可选加
event.dataTransfer.dropEffect = 'move'控制光标样式,但不影响投放逻辑
dataTransfer.getData 在跨 iframe 或 Firefox 下大概率失效
别依赖 event.dataTransfer.getData('text/plain') 拿卡片 ID —— Firefox 和跨 iframe 场景下它常返回空字符串。尤其看板卡片由 React/Vue 动态渲染,event.target 也容易指向子元素而非原始卡片节点。
- 用
WeakMap绑定拖拽上下文,比存字符串可靠得多 - 在
dragstart里:dragState.set(e.dataTransfer, { cardId: element.dataset.id, sourceColumn: element.closest('.column')?.dataset.columnId }) - 在
drop里直接取:const { cardId, sourceColumn } = dragState.get(event.dataTransfer) - 记得在
dragend后调用dragState.delete(e.dataTransfer),否则内存泄漏
移动端原生 draggable 基本不可用,别硬扛
iOS Safari 和 Android Chrome 都不触发 dragstart/drop,draggable="true" 在移动端等于摆设。强行适配原生 API 会陷入坐标计算失准、滚动冲突、长按延迟等泥潭。
立即学习“前端免费学习笔记(深入)”;
- 检测
'ontouchstart' in window,走降级路径 - 推荐用
touchstart/touchmove/touchend+CSS transform手动模拟,或引入interact.js这类已做平台兼容的轻量库 - 如果坚持原生,至少把拖拽激活改为“长按 300ms 后进入拖拽模式”,避免误触
真正难的不是监听几个事件,而是源列和目标列之间 DOM 插入时机与数据状态的原子性对齐——松手那一帧,DOM 移动、数组更新、滚动位置修正、无障碍焦点迁移,必须全部完成,缺一不可。



















