背景:
在使用 Ueditor 编辑器的时候,发现在编辑器中输入文字时会出现,在输入几个文字后,就会随机光标回到最开始的位置
核心解决方案:切换到 listener 模式
唯一有效的修改:
<vue-ueditor-wrap v-model="content" :config="editorConfig" mode="listener" ...></vue-ueditor-wrap>
observer 模式 vs listener 模式深度解析
vue-ueditor-wrap 的两种监听模式
vue-ueditor-wrap 提供了两种监听 UEditor 内容变化的方式:
1. observer 模式(默认模式,有问题)
工作原理:
- 使用浏览器的
MutationObserver
API 监听 DOM 变化 - 当编辑器内容发生任何 DOM 变化时触发
- 将 DOM 变化转换为内容更新事件
技术特点:
// 内部实现类似
const observer = new MutationObserver((mutations) => {
// 检测到DOM变化
const content = editor.getContent();
this.$emit('input', content); // 触发v-model更新
});
observer.observe(editorElement, {
attributes: true,
childList: true,
subtree: true,
characterData: true,
});
问题所在:
- 过度敏感: 监听所有 DOM 变化,包括 UEditor 内部的 DOM 操作
- 循环触发: DOM 变化 → v-model 更新 → Vue 重渲染 → DOM 变化 → 无限循环
- 光标干扰: DOM 重构过程中浏览器失去光标位置跟踪
- 性能问题: 频繁的 DOM 监听和事件触发
2. listener 模式(推荐模式,无问题)
工作原理:
- 直接监听 UEditor 官方的
contentChange
事件 - 依赖 UEditor 内置的内容变化检测机制
- 避免直接操作 DOM 监听
技术特点:
// 内部实现类似
editor.addListener('contentChange', () => {
const content = editor.getContent();
this.$emit('input', content); // 触发v-model更新
});
优势:
- 事件驱动: 基于 UEditor 官方事件,更可靠
- 避免循环: 不会因为 DOM 变化而重复触发
- 光标稳定: 不干扰 UEditor 的 DOM 管理
- 性能更好: 事件触发频率更合理
详细对比表
特性 | observer 模式 | listener 模式 |
---|---|---|
监听机制 | MutationObserver API | UEditor contentChange 事件 |
触发频率 | 极高(每次 DOM 变化) | 适中(内容真正变化时) |
光标稳定性 | ❌ 不稳定,会跳转 | ✅ 稳定,保持位置 |
性能影响 | ❌ 高(频繁 DOM 监听) | ✅ 低(事件驱动) |
兼容性 | ✅ 现代浏览器支持好 | ⚠️ 依赖 UEditor 事件完整性 |
监听准确性 | ⚠️ 过度敏感 | ✅ 准确反映内容变化 |
循环风险 | ❌ 容易产生无限循环 | ✅ 避免循环触发 |
为什么 observer 模式会导致光标跳转?
DOM 监听过度敏感
用户输入 → UEditor内部DOM操作 → MutationObserver触发 → Vue响应式更新 → DOM重渲染 → 光标位置丢失
Vue 重渲染干扰
- MutationObserver 检测到 DOM 变化
- 触发 v-model 更新到父组件
- Vue 响应式系统重新渲染组件
- UEditor 的 DOM 结构被 Vue 重新处理
- 浏览器失去原始光标位置引用
时序竞争问题
- UEditor 和 Vue 同时操作同一 DOM 区域
- 产生时序竞争和状态不一致
- 导致光标位置计算错误
为什么 listener 模式能解决问题?
事件边界清晰
用户输入 → UEditor处理 → contentChange事件 → Vue更新 → 不干扰UEditor的DOM
避免 DOM 竞争
- 只在 UEditor 确认内容变化后才触发
- 不监听中间的 DOM 操作过程
- UEditor 保持对自己 DOM 的完全控制权
光标管理分离
- Vue 只负责数据同步
- UEditor 负责 DOM 和光标管理
- 两者职责分离,互不干扰