原生不支持拖拽排序,必须用javascript拦截drag事件并手动重排dom;直接移动会破坏序号逻辑,需清除所有value属性让浏览器按新dom顺序重算编号,否则视觉与语义脱节。

原生 <ol></ol> 不支持拖拽排序,必须用 JavaScript 拦截 drag 事件并手动重排 DOM —— 但直接操作 <ol></ol> 的 <li> 会破坏序号逻辑,除非你同步重写 value 属性或放弃自动编号。
为什么不能直接给 <ol><li></ol> 加 draggable="true"
浏览器对 <ol></ol> 的序号渲染是强耦合的:<li value="3"> 后面的项会自动按 4、5 递增;一旦你用 insertBefore() 移动 <li>,序号不会重算,视觉和 DOM 顺序就脱节了。更麻烦的是,dragstart 中若不设 dataTransfer.setData(),Safari 直接拒绝触发 drop。
- 别指望
<ol reversed></ol>或 CSSflex-direction: column-reverse实现拖拽排序——它们只改显示,不改 DOM 顺序,拖完位置还是错的 -
<ol></ol>的start属性只影响起始编号,无法动态修正中间断点 - 用
counter-reset+counter-increment重写序号样式?可以,但 screen reader 和打印样式仍读原始 DOM 顺序,可访问性受损
drop 时如何安全插入并保持序号连贯
核心不是“移动元素”,而是“重建序号链”。最稳的方式是:拖放完成后,遍历所有 <li>,统一清除 value 属性,让浏览器按自然顺序重算。但前提是 DOM 顺序已正确。
- 在
drop回调里,先用insertBefore()或appendChild()把拖拽项放到目标位置(注意:目标必须是<li>或<ol></ol>自身,不能是文本节点) - 插入后立即执行重置:
ol.querySelectorAll('li').forEach((li, i) => li.removeAttribute('value')),强制浏览器从 1 开始编号 - 如果需要自定义起始值(比如从 100 开始),只在第一个
<li>上设value="100",其余清空 - 避免在
dragover里频繁调用preventDefault()外还做 DOM 查询——会卡顿,尤其列表项多时
移动端和 Safari 的硬伤怎么绕
Safari(包括 iOS)对 <ol></ol> 内 draggable 支持极差,dragstart 可能根本不触发;而所有移动端 Chrome/Firefox 也默认禁用原生 drag API。这不是 bug,是规范限制。
- 检测
'draggable' in document.createElement('span')且typeof TouchEvent !== 'undefined',为真就降级到 touch 模拟方案 - 不要手写 touch 拖拽逻辑——用
SortableJS并配置forceFallback: true,它内部用touchmove+transform位移,再通过onEnd回调更新<ol></ol>DOM - 若坚持不用库,至少把
touchstart的target缓存为draggedItem,后续touchmove计算距离,touchend执行插入,否则手指一抬就丢失上下文 - 测试时用真机连 Safari Web Inspector,桌面端模拟的 touch 事件无法复现 Safari 对
dataTransfer的拦截逻辑
真正难的不是写通拖拽,而是让序号、DOM 顺序、可访问性、移动端体验四者同时成立。多数人卡在 dragover.preventDefault() 漏写,或者以为设了 draggable="true" 就万事大吉——结果 Safari 里拖不动,屏幕阅读器念出乱序数字,用户拖完发现序号断层。这些点不逐个验证,上线就是线上事故。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











