draggable="true"仅触发拖拽事件,不自动排序或移动dom;需手动处理dragover阻止默认行为、weakmap存储拖拽状态、children索引计算插入位置,并为移动端模拟touch拖拽逻辑。

draggable="true"只是开关,不是排序逻辑
设了 draggable="true" 只会让浏览器派发 dragstart、dragover、drop 这些事件,它不帮你移动 DOM、不维护顺序、不跨容器同步状态。看板里每张卡片都加这个属性,只是“允许拖”,不代表拖完就自动插到目标列中间——你得自己算位置、自己 insertBefore()、自己更新数据数组。
常见错误是只监听 drop,却漏掉 dragover 里的 event.preventDefault(),结果拖进列里图标变“禁止”,松手后啥也没发生。这不是 bug,是浏览器默认策略:非表单/链接元素禁止投放,必须显式放行。
跨列拖拽必须用 WeakMap 存源信息
dataTransfer.getData() 在 Firefox 和跨 iframe 场景下大概率返回空字符串,别指望它稳定传 ID 或索引。尤其看板里卡片可能动态渲染、组件复用,靠 event.target 拿不到原始数据。
稳妥做法是用 WeakMap 绑定拖拽上下文:
const dragState = new WeakMap();
element.addEventListener('dragstart', e => {
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),防内存泄漏。
同列排序别依赖 clientY,用 children 索引更稳
用鼠标 Y 坐标去比对每个卡片的 getBoundingClientRect().top 容易因滚动、缩放、字体渲染差异出错。尤其 Flex 布局下卡片高度不均时,计算插入点容易偏一两个像素。
推荐方案是把目标列所有子节点转成数组,用 Array.from(column.children),再遍历找最靠近鼠标位置的那个相邻节点:
- 拖到卡片 A 和 B 中间 → 插入位置为
B的index - 拖到列首 → 插入位置为
0 - 拖到列尾 → 插入位置为
children.length
这样不依赖视觉坐标,也不怕滚动条干扰,DOM 更新后顺序一定和数组一致。
移动端拖拽必须降级处理
原生 draggable 在 iOS Safari 和部分安卓 WebView 里基本不可用——它不响应 touch 事件,dragstart 根本不触发。不能简单加 touchstart 就完事,得整套模拟。
实操要点:
- 检测
'ontouchstart' in window,开启 touch 模式 - 用
touchmove+requestAnimationFrame模拟悬浮位移,避免卡顿 - 禁用
passive: true的touchmove监听器,否则preventDefault()失效 - 拖拽中临时加
pointer-events: none到非目标列,防止误触滚动条
复杂点在于:touch 模拟要同步维护与桌面端一致的 dragState 结构,否则跨端状态不同步。最容易被忽略的是,touch 拖拽结束时没触发等效的 drop 逻辑,导致卡片“悬停”在半空不落位。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











