VueJS集成Quill编辑器删除文本时的异常问题求助
Vue3集成Quill编辑器的删除异常问题
删除2-3个以上字符后光标跳转到第一行,控制台偶尔抛出TypeError: leaf.position is not a function错误,排查发现问题出在setContents或setText调用环节。以下是我的代码:
<template> <div class="app-text-editor"> <div id="toolbar"> <div class="ql-formats"> <select class="ql-size"> <option value="small" /> <option selected /> <option value="large" /> <option value="huge" /> </select> </div> <div class="ql-formats"> <select class="ql-color"> <option selected /> <option value="red" /> <option value="orange" /> <option value="yellow" /> <option value="green" /> <option value="blue" /> <option value="purple" /> </select> <select class="ql-background"> <option selected /> <option value="red" /> <option value="orange" /> <option value="yellow" /> <option value="green" /> <option value="blue" /> <option value="purple" /> </select> </div> <div class="ql-formats"> <button class="ql-bold" /> <button class="ql-italic" /> <button class="ql-underline" /> <button class="ql-strike" /> </div> <div class="ql-formats"> <button class="ql-list" value="ordered" /> <button class="ql-list" value="bullet" /> <select class="ql-align"> <option selected /> <option value="center" /> <option value="right" /> <option value="justify" /> </select> </div> </div> <div id="editor" /> </div> </template> <script setup> import { onMounted, ref } from 'vue' import { useI18n } from 'vue-i18n' import Quill from 'quill' import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' const props = defineProps({ modelValue: { type: String, default: null } }) const emits = defineEmits(['update:modelValue']) const i18n = useI18n() const quill = ref(null) onMounted(() => { var options = { modules: { toolbar: '#toolbar' }, theme: 'snow', placeholder: i18n.t('app-text-editor.type-text') } quill.value = new Quill('#editor', options) setText() watchText() }) const watchText = () => quill.value.on('text-change', () => emits('update:modelValue', quill.value?.root?.innerHTML)) const setText = () => { const text = props.modelValue const delta = quill.value.clipboard.convert(text) quill.value.setContents(delta, 'silent') } </script> <style lang="scss" scoped> .app-text-editor { height: 100%; display: flex; flex-direction: column; #toolbar, #editor { background-color: var(--color-light-grey); border: none; } #toolbar { border-radius: 1.25rem 1.25rem 0 0; } #editor { border-radius: 0 0 1.25rem 1.25rem; } } </style>
问题根源及修复方案
核心问题
- 双向绑定循环触发:
text-change事件直接触发modelValue更新,若父组件回传内容未做判断,会再次调用setText重写编辑器内容,导致内部状态混乱,光标跳转。 clipboard.convert误用:该方法用于处理剪贴板内容,直接转换HTML字符串会生成不符合Quill Delta规范的结构,引发内部方法调用错误。- 缺少
modelValue变化监听:仅在初始化时设置内容,父组件更新modelValue时编辑器无法同步,同时未阻断自触发更新循环。
修改后的完整代码
<template> <div class="app-text-editor"> <div id="toolbar"> <div class="ql-formats"> <select class="ql-size"> <option value="small" /> <option selected /> <option value="large" /> <option value="huge" /> </select> </div> <div class="ql-formats"> <select class="ql-color"> <option selected /> <option value="red" /> <option value="orange" /> <option value="yellow" /> <option value="green" /> <option value="blue" /> <option value="purple" /> </select> <select class="ql-background"> <option selected /> <option value="red" /> <option value="orange" /> <option value="yellow" /> <option value="green" /> <option value="blue" /> <option value="purple" /> </select> </div> <div class="ql-formats"> <button class="ql-bold" /> <button class="ql-italic" /> <button class="ql-underline" /> <button class="ql-strike" /> </div> <div class="ql-formats"> <button class="ql-list" value="ordered" /> <button class="ql-list" value="bullet" /> <select class="ql-align"> <option selected /> <option value="center" /> <option value="right" /> <option value="justify" /> </select> </div> </div> <div id="editor" /> </div> </template> <script setup> import { onMounted, ref, watch } from 'vue' import { useI18n } from 'vue-i18n' import Quill from 'quill' import 'quill/dist/quill.core.css' import 'quill/dist/quill.snow.css' const props = defineProps({ modelValue: { type: String, default: '' } }) const emits = defineEmits(['update:modelValue']) const i18n = useI18n() const quill = ref(null) let isUpdating = false // 标记内部更新,阻断循环 onMounted(() => { const options = { modules: { toolbar: '#toolbar' }, theme: 'snow', placeholder: i18n.t('app-text-editor.type-text') } quill.value = new Quill('#editor', options) // 初始化内容 if (props.modelValue) { quill.value.root.innerHTML = props.modelValue } // 监听编辑器内容变化 quill.value.on('text-change', () => { if (!isUpdating) { const html = quill.value.root.innerHTML emits('update:modelValue', html) } }) }) // 监听父组件传入的modelValue变化,同步到编辑器 watch(() => props.modelValue, (newVal) => { if (quill.value && newVal !== quill.value.root.innerHTML) { isUpdating = true // 直接设置innerHTML更适配HTML内容同步,避免clipboard.convert的兼容性问题 quill.value.root.innerHTML = newVal isUpdating = false } }) </script> <style lang="scss" scoped> .app-text-editor { height: 100%; display: flex; flex-direction: column; #toolbar, #editor { background-color: var(--color-light-grey); border: none; } #toolbar { border-radius: 1.25rem 1.25rem 0 0; } #editor { border-radius: 0 0 1.25rem 1.25rem; } } </style>
关键修改说明
- 增加
isUpdating标记,阻断编辑器自身触发的modelValue更新引发的重绘,避免光标异常跳转。 - 替换
clipboard.convert为直接设置root.innerHTML,更适配HTML内容的同步,避免Delta结构不兼容问题。 - 添加
watch监听modelValue变化,实现父组件到编辑器的双向同步,同时对比内容避免无效更新。 - 初始化内容时直接赋值
innerHTML,减少setContents带来的内部状态冲突。
内容的提问来源于stack exchange,提问作者Charles
相关产品推荐
相关产品推荐

