Vue3项目中Quill编辑器offset初始化错误修复方案
Vue3集成Quill编辑器修改笔记时offset空值错误的修复
问题描述
创建笔记功能正常,但进入修改笔记页面后,点击或输入Quill编辑器时会触发如下空值错误:
quill.js?v=870ff25b:12570 Uncaught TypeError: Cannot read properties of null (reading 'offset') at quill.js?v=870ff25b:12570:26 at Array.map (<anonymous>) at Selection.normalizedToRange (quill.js?v=870ff25b:12567:31) at Selection.getRange (quill.js?v=870ff25b:12556:25) at Selection.update (quill.js?v=870ff25b:12691:43) at quill.js?v=870ff25b:12452:12 at quill.js?v=870ff25b:12338:9 at Array.forEach (<anonymous>) at Emitter.handleDOM (quill.js?v=870ff25b:12332:43) at quill.js?v=870ff25b:12310:23
当前实现代码
<template> <section id="view-note-page" class="container mt-4"> <div class="note-header-container mb-3 d-flex justify-content-between align-items-center"> <div class="note-title-container"> <label for="note-title" class="note-title-label">Title:</label> <input id="note-title" type="text" v-model="noteData.title" class="note-title-input" placeholder="Enter note title" /> </div> <button class="btn btn-primary btn-save-changes" @click="saveNote">Save Changes</button> </div> <div id="post-editor" class="mb-3 flex-grow-1"> <div id="quill-editor"></div> </div> </section> </template> <script> import { nextTick } from "vue"; import Quill from "quill"; import "quill/dist/quill.snow.css"; import { toast } from "vue3-toastify"; import "vue3-toastify/dist/index.css"; import { noteService } from "@/services/note.service"; export default { name: "ViewNotePage", props: { noteId: { type: [String, Number], required: true, }, }, data() { return { noteData: { title: "", content: "", }, quillEditor: null, }; }, methods: { async fetchNote() { try { const note = await noteService.fetchNoteFromId(this.noteId); this.noteData = note; await this.updateQuillContent(note.content); } catch (error) { console.error("Failed to fetch the note:", error); toast.error("Failed to fetch the note.", { autoClose: 2000 }); } }, async initializeQuill() { try { await nextTick(); const editorElement = document.getElementById("quill-editor"); if (editorElement) { this.quillEditor = new Quill(editorElement, { theme: "snow", modules: { toolbar: [ ["bold", "italic", "underline"], [{list: "ordered"}, {list: "bullet"}], [{header: [1, 2, 3, false]}], ["link"], ["clean"], ], }, }); this.quillEditor.on("text-change", () => { this.noteData.content = JSON.stringify(this.quillEditor.getContents()); }); } } catch (error) { console.error("Failed to initialize Quill editor:", error); } }, async updateQuillContent(content) { try { if (this.quillEditor) { const delta = content ? JSON.parse(content) : {ops: []}; this.quillEditor.setContents(delta); } } catch (error) { console.error("Failed to update Quill content:", error); } }, async saveNote() { try { if (!this.noteData.title || !this.noteData.content) { toast.error("Please provide a title and content for the note.", {autoClose: 2000}); return; } await noteService.updateNote(this.noteId, this.noteData); toast.success("Note updated successfully!", {autoClose: 2000}); this.$emit("update", this.noteData); } catch (error) { console.error("Failed to save the note:", error); toast.error("Failed to update the note.", {autoClose: 2000}); } }, }, async mounted() { try { await this.initializeQuill(); await this.fetchNote(); } catch (error) { console.error("Error during component setup:", error); } }, watch: { noteId: { immediate: true, handler(newNoteId) { this.fetchNote(); }, }, }, }; </script>
已尝试无效方案
曾尝试将Quill实例声明为组件外变量的方案,但问题未解决:
// ✅ works with quill2 let quill: Quill | null = null onMounted(() => { quill = new Quill(...) })
有效修复方法
修复思路
该错误核心原因是:noteId变化时直接更新编辑器内容,导致旧Quill实例与当前DOM绑定冲突,或者编辑器选区状态异常。同时异步初始化顺序也可能引发内容更新时机不匹配问题。
具体修复步骤
监听
noteId变化时,销毁旧实例再重新初始化
修改noteId的watch处理器,先清除旧的Quill实例和DOM残留,再重新初始化编辑器并加载新内容:watch: { noteId: { immediate: true, async handler(newNoteId) { // 销毁旧编辑器实例 if (this.quillEditor) { this.quillEditor = null; const editorElement = document.getElementById("quill-editor"); if (editorElement) { editorElement.innerHTML = ''; } } // 重新初始化编辑器并加载新笔记 await this.initializeQuill(); await this.fetchNote(); }, }, },设置内容后手动重置编辑器选区
在updateQuillContent方法中,添加选区重置逻辑,避免Quill自动计算选区时出现空值:async updateQuillContent(content) { try { if (this.quillEditor) { const delta = content ? JSON.parse(content) : {ops: []}; this.quillEditor.setContents(delta); // 将光标定位到内容末尾,重置选区状态 this.quillEditor.setSelection(this.quillEditor.getLength() - 1, 0); } } catch (error) { console.error("Failed to update Quill content:", error); } },初始化编辑器前清空DOM元素
优化initializeQuill方法,确保每次初始化时编辑器DOM元素是干净的:async initializeQuill() { try { await nextTick(); const editorElement = document.getElementById("quill-editor"); if (editorElement) { // 清空DOM残留内容 editorElement.innerHTML = ''; this.quillEditor = new Quill(editorElement, { theme: "snow", modules: { toolbar: [ ["bold", "italic", "underline"], [{list: "ordered"}, {list: "bullet"}], [{header: [1, 2, 3, false]}], ["link"], ["clean"], ], }, }); this.quillEditor.on("text-change", () => { this.noteData.content = JSON.stringify(this.quillEditor.getContents()); }); } } catch (error) { console.error("Failed to initialize Quill editor:", error); } },
修复原理
- 销毁旧实例并重新初始化:解决
noteId切换时,旧实例与当前DOM绑定冲突导致的选区计算异常。 - 手动重置选区:避免Quill自动计算选区时,因内容更新引发的空DOM节点引用。
- 清空DOM元素:确保每次初始化编辑器时,没有残留的旧内容或编辑器生成的DOM结构干扰。
内容的提问来源于stack exchange,提问作者Chinatsu
相关产品推荐
相关产品推荐

