列内拖拽排序本质是dom节点顺序调整,需在drop时用insertbefore确定插入位置;跨列拖拽需通过坐标计算预判插入点;sortablejs默认配置不适应复杂布局;ios safari应改用长按+手势模拟。

用 dragstart 和 dragover 实现列内拖拽排序
列内任务上下拖动重排,本质是 DOM 节点顺序调整,不需要后端介入。关键不是“拖”,而是“放”——drop 事件触发时,得知道该插到目标元素的前面还是后面。
常见错误是只监听 dragover 却没调用 event.preventDefault(),导致浏览器默认行为拦截了 drop;或者在 drop 里直接 appendChild,结果所有任务都堆到底部。
- 给每个任务卡片(
<li>或<div class="task">)设 <code>draggable="true" - 在任务元素上监听
dragstart,把当前元素 ID 存进dataTransfer.setData('text/plain', id) - 列容器监听
dragover,必须立刻执行event.preventDefault(),否则drop不会触发 - 列容器监听
drop,用document.elementFromPoint(x, y)或遍历子元素判断鼠标悬停位置,再用insertBefore(draggedEl, targetEl)插入 - 给每列容器加
data-column-id,比如data-column-id="todo" - 列容器监听
dragenter时加临时样式(如border: 2px dashed #666),提供视觉反馈 - 在
dragover中持续计算鼠标相对于列容器的 Y 坐标,结合子任务高度,预判插入点(例如:Y 在某任务下半部分 → 插到它后面) -
drop触发时,从dataTransfer.getData('text/plain')拿到被拖任务 ID,再根据预判位置执行移动 - 必须显式配置
group.name让多列归属同一拖拽组 - 用
handle选项限定只有任务标题区域可拖,避免误触操作按钮 - 禁用
sort: false在非目标列,防止拖离时意外重排 - 自定义
onMove函数,用evt.draggedRect和evt.relatedRect手动比对坐标,决定是否允许放入 - 检测
navigator.userAgent.includes('iPhone') || navigator.userAgent.includes('iPad') - 移动端改用
touchstart+touchmove模拟拖拽:长按 500ms 后,将任务绝对定位并跟随手指移动 - 松手(
touchend)时,用document.elementFromPoint查目标列,再走和桌面端一样的插入逻辑 - 记得移除
draggable="true"和所有原生拖拽监听,避免冲突
跨列拖拽时如何识别目标列和插入位置
跨列拖拽比列内复杂:既要识别拖进了哪一列,又要判断是想放在该列顶部、中间还是底部。浏览器不提供“悬停在哪两个任务之间”的原生信息,得自己算。
典型翻车场景:拖到列边缘时,elementFromPoint 返回的是列容器本身,不是内部任务;或者拖太快,drop 触发时鼠标已移出,拿不到有效 target。
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
为什么不要用 SortableJS 默认配置直接套看板
SortableJS 确实省事,但它的默认跨列拖拽逻辑假设所有列是平级 <ul></ul>,且任务都是同级 <li>。一旦你用了 flex 布局、带 header 的列结构,或需要拖拽时高亮“插入线”,它就容易错位或失效。
更隐蔽的问题是:SortableJS 的 onEnd 回调里,evt.from 和 evt.to 指向的是父容器,但如果你列容器是 <section></section> 包着 <header>+<ul></ul></header>,实际任务节点不在 evt.to 直接子级,SortableJS 就会找不到插入点。
移动端 Safari 上拖拽失效的硬伤怎么绕过
iOS Safari 对原生 drag/drop 支持极差:不触发 dragstart,dataTransfer 为空,甚至整个事件链静默失败。这不是 bug,是 Apple 明确不支持——所以别白费劲配 touch-action: none 或模拟事件。
真实项目里,90% 的看板在 iPad 上靠长按 + 手势位移实现,而不是等 Safari 某天修复。










