可用原生HTML+CSS+JS实现稳定看板系统,需绕过浏览器限制:结构用语义化div(board/column/card),dragstart传ID、dragover/drop必preventDefault,列内排序靠Y坐标计算插入位置,状态存轻量ID序列而非DOM。

直接用 HTML + CSS + JavaScript 就能做出可拖拽、跨列、状态可存的看板系统,关键不在“能不能”,而在“怎么绕过浏览器默认限制、让逻辑稳得住”。原生 drag/drop 是起点,但必须手动补足三件事:位置判断、状态同步、视觉反馈。
结构要干净,别让 DOM 干扰逻辑
不用 section 或 article 套壳,就用语义清晰的 div:
- 外层
<div class="board">作为总容器 - 每列是
<div class="column" data-column-id="todo">,带唯一标识 - 每张卡片是
<div class="card" draggable="true" data-card-id="task-123">,必须设draggable="true" - 列内用
flex-direction: column; gap: 8px堆叠卡片,避免 margin 折叠干扰拖拽定位
拖拽逻辑必须写死这三步
漏掉任意一个,就会出现“拖不动”“松手没反应”“卡片消失”:
-
dragstart 里:调用
e.dataTransfer.setData('text/plain', card.dataset.id),只传 ID,别传 HTML 或 JSON(Safari 对 MIME 类型敏感) -
dragover 里:对列容器监听,立刻
e.preventDefault()——这是触发 drop 的前提,不是可选项 -
drop 里:先
e.preventDefault(),再用e.target.closest('.column')找到目标列,最后移除原卡片、插入新位置
列内排序靠坐标,不是靠 hover
同列拖拽重排不能靠“悬停到某张卡上”,而要实时算鼠标 Y 坐标落点:
立即学习“前端免费学习笔记(深入)”;
- 在
dragover中获取当前列所有卡片元素(排除自己),遍历调用getBoundingClientRect() - 对比
e.clientY和每个卡片的上下中线,决定插在它前面还是后面 - 用
list.insertBefore(card, target)精确插入,别用appendChild简单追加
状态保存别存 DOM,存轻量结构
刷新后任务回原位,靠的是序列化顺序,不是把 innerHTML 整段存 localStorage:
- 定义数据模型如
{ todo: ['task-1', 'task-3'], doing: ['task-2'] } - 每次 drop 后更新对应数组,再
localStorage.setItem('kanban-state', JSON.stringify(state)) - 页面加载时读取并按序渲染卡片,不依赖 DOM 顺序还原
不复杂但容易忽略



















