原生 drag api 在编辑器中不推荐使用——易与文本选中、光标定位冲突且移动端失效;应优先选用 sortablejs 或 @dnd-kit/sortable,它们通过事件模拟拖拽、兼容 contenteditable,并需注意 key 稳定性、数据同步及编辑器状态一致性。

原生 drag API 在网页编辑器里做列表拖拽排序,能用但不推荐直接上——它在编辑器上下文中极易和文本选中、光标定位、内容editable冲突,且移动端完全失效。真要稳定交付,优先用 SortableJS 或基于 @dnd-kit/sortable 的封装方案。
为什么原生 drag 事件在编辑器里大概率翻车
编辑器容器(如 contenteditable="true" 的 div 或富文本框架)会劫持鼠标/键盘事件,导致:dragstart 根本不触发;dragover 被吞掉;松手后光标跳到奇怪位置甚至清空选区。浏览器对 contenteditable + draggable 的组合支持极差,不是 bug,是规范没定义行为。
-
draggable="true"加在li上,但父级contenteditable容器会拦截mousedown,dragstart永远不会来 - 即使强行触发,
e.dataTransfer.setData()在 Safari 和部分 WebView 中对非文件类型静默失败 - 拖拽过程中用户可能意外双击选中文本,
drop逻辑被中断,DOM 状态错乱 - 没有
ghost占位提示,用户无法判断“松手后到底插在哪”
用 SortableJS 避开 90% 的坑
它不依赖原生 drag,而是监听 touchstart/mousedown + mousemove/touchmove,用 transform: translateY() 模拟拖拽,天然兼容编辑器环境。
- 必须设
swapThreshold: 10(默认 30 太钝),否则小尺寸列表项拖不动 - 务必配
ghostClass: "sortable-ghost"并写 CSS:.sortable-ghost { opacity: 0.6; },否则看不到占位效果 - 若列表项含
input、button或可编辑区域,加preventOnFilter: false,否则点击控件会误触发拖拽 - 动态增删项后,调用
sortable.destroy()再重新初始化,别只改 DOM 不重绑
Vue/React 编辑器中数据同步的关键点
框架绑定拖拽库时,最常出问题是视图排了序,但源数组没变——下次 re-render 就打回原形。
- Vue 中用
v-sortable,必须监听end事件并用this.$nextTick更新数组:this.list = [...this.list],不能直接赋值新引用 - React 用
@dnd-kit/sortable,onDragEnd里必须函数式更新:setItems(items => arrayMove(items, oldIndex, newIndex)),arrayMove来自@dnd-kit/utilities - 所有列表项的
key必须基于稳定 ID(如item.id),绝不能用index,否则拖拽后组件状态丢失 - 如果编辑器用的是 Slate / TipTap 等框架,优先查其官方插件(如
slate-dnd),别硬套通用排序库
真正难的不是拖拽动作本身,而是拖拽前后编辑器光标、选区、撤销栈(undo stack)的状态一致性。原生 API 没提供这些钩子,第三方库也得配合编辑器 API 手动 patch。上线前务必在 iOS Safari 和 Chrome Android 上实机测试拖拽+输入+撤销三连操作是否断裂。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











