draggable="true"仅触发拖拽行为,不参与状态持久化;真正需存储的是各列id及其对应的卡片id数组,如{"todo":["task-1","task-3"],"doing":["task-2"],"done":["task-4"]},以实现可靠的状态恢复。

draggable="true" 本身不参与状态持久化,它只是触发拖拽行为的开关;真正要存的,是卡片在各列中的顺序和归属列 ID。
为什么只设 draggable="true" 不足以支撑看板状态恢复
浏览器不会自动记住你把 card-7 拖到了「已完成」列——draggable 属性只影响 DOM 元素能否被拖动,不携带语义、不绑定数据、也不触发存储逻辑。刷新后所有卡片默认回到初始父容器,除非你主动干预。
常见错误现象:
- 页面重载后卡片全部堆在第一列(DOM 顺序没变,但视觉上“乱了”)
- 用
innerHTML或outerHTML存整个列内容 → 后续加个 class 或改个事件监听器,JSON.parse 就报错 - 只存了卡片 ID 列表,但没存每个 ID 对应哪一列 → 恢复时不知道该 append 到
.todo还是.done
localStorage 应该存什么结构才可靠
必须存轻量、稳定、与 DOM 结构解耦的数据。推荐用对象映射列 ID 到卡片 ID 数组:
{
"todo": ["task-1", "task-3"],
"doing": ["task-2", "task-5"],
"done": ["task-4"]
}
这样做的好处:
- 列名(如
todo)对应真实 DOM 的data-column-id="todo",不依赖 class 名或顺序 - 卡片 ID 是稳定标识(来自
data-id),不是 index 或临时生成的 key - 增删卡片只需操作数组,不用拼 HTML 字符串,避免 XSS 和解析失败
- 支持跨列移动:拖一个卡片过去,只改它在哪个数组里,不碰其他列
拖拽结束时怎么正确更新 localStorage
别在 drop 事件里直接读 event.target.children —— 它返回的是当前时刻的 DOM 节点列表,但 React/Vue 可能还没重渲染,或者有动画过渡帧干扰。
更稳的做法是:
- 在
dragstart中记下被拖卡片的原始列 ID:const fromColumn = card.closest('[data-column-id]').dataset.columnId - 在
drop事件中,从目标列容器拿到它的dataset.columnId - 用这两个 ID 更新上面那个对象结构,再
localStorage.setItem('kanbanState', JSON.stringify(newState)) - 不要用
Array.from(el.children).map(c => c.dataset.id)实时抓顺序——它在拖拽动画未完成时可能漏掉节点
加载时如何安全还原卡片位置
还原不是“把卡片一个个 append 进去”,而是按列清空 + 批量插入,避免重复绑定事件或样式残留:
- 先遍历所有列容器,清空其子节点(但保留列自身)
- 再根据
localStorage里的数组,用document.querySelector(`[data-id="${id}"]`)找到对应卡片 - 对每个卡片调用
column.appendChild(card),顺序自然由数组索引决定 - 如果某张卡片在 DOM 中已不存在(比如被用户删过),跳过即可,不报错
容易被忽略的一点:还原前确保所有卡片 DOM 已就绪。若卡片是异步加载或懒渲染的,得等它们 mount 完再执行 restore 逻辑,否则 querySelector 找不到元素。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











