HTML5原生多列表拖拽需明确三要素:任务项设draggable="true"并dragstart中setData存ID、列容器监听dragover/drop且preventDefault、drop时计算插入位置或数据驱动重渲染,同时补视觉反馈并为移动端降级。

用 HTML5 原生 API 实现多列表拖拽(比如 Kanban 看板中任务在“待办”“进行中”“已完成”之间拖动),关键不是堆事件,而是理清三件事:谁可拖、哪能接、拖完怎么更新。它不依赖任何库,但必须手动管好数据和 DOM 的一致性。
让每个任务项真正可拖
给每个任务卡片(如 <li> 或 <div class="task">)设置 draggable="true",并在 dragstart 中存唯一标识:
- 用
e.dataTransfer.setData('text/plain', taskId)存 ID,兼容性最好,也避免跨域问题 - 别只写
draggable="true"却漏掉setData——没这步,drop里取不到任何信息,整个链路就断了 - 加一句
e.dataTransfer.effectAllowed = 'move',明确告诉浏览器这是移动操作,防止误触发复制图标 - 顺手加
user-select: none到任务项 CSS,避免拖拽时意外选中文本
让每列都“愿意接住”拖拽
每列容器(如 <section class="column" data-status="todo">)必须响应拖入,核心是两件事都做:
- 监听该列的
dragover,开头就写e.preventDefault()——这是硬性要求,不写就根本进不了drop - 同时监听同一列的
drop,开头也必须写e.preventDefault(),否则浏览器会按默认行为打开链接或下载文件 - 列容器要有实际高度(哪怕空列表),加
min-height: 20px或伪元素撑开,不然拖进去没反应 - 别在
dragenter里写preventDefault,它不持续触发;靠dragover才稳
拖到哪一列、插在哪一行,得自己算
drop 触发时,不能只看鼠标点在哪,要结合目标列和当前悬停位置判断逻辑落点:
立即学习“前端免费学习笔记(深入)”;
- 用
e.target.closest('.column')定位目标列,再读它的dataset.status(如"in-progress") - 再用
e.target.closest('li')找最近的任务项(防止点到空白处返回null) - 如果找到了
li,计算鼠标 Y 坐标是否过其中线:e.clientY > rect.top + rect.height / 2,决定插在它前面还是后面 - 插入前务必检查
dragged === targetLi,否则insertBefore(dragged, dragged)会报错 - 更推荐“数据驱动”:先更新 JS 数组里该任务的
status和排序索引,再全量重渲染整列,避免 DOM 和状态错位
视觉反馈和边界情况不能少
原生 API 不带预览线、不自动高亮,这些都要手动补:
- 在
dragenter时给目标列加class="drag-over",配合 CSS 显示边框或背景色 - 在
dragleave或dragend里及时移除,避免样式残留 - 为
[draggable="true"]加cursor: move,拖拽时鼠标图标才自然 - 移动端不支持原生 drag API,iOS Safari 和多数安卓 WebView 默认禁用
draggable——生产环境必须降级,比如用SortableJS或基于touchstart/move/end自研



















