一、Vue3项目为啥优先选Quill做富文本?
做前端项目时,富文本编辑需求太常见了!从简单的文章排版到复杂的富媒体嵌入,选对工具能省不少事儿,要是你用Vue3开发,想结合Quill实现富文本功能,肯定一堆疑问:选Quill有啥优势?咋快速集成?自定义工具栏、加拓展功能咋搞?性能和兼容性坑咋填?今天咱就把这些常见问题掰开揉碎,用白话讲明白~
先唠唠富文本编辑器的选型逻辑,市面上常见的有TinyMCE、Slate、Quill这些,Quill能在Vue3项目里“杀出重围”,核心是这几点:
开发体验友好,API灵活度高
Quill用Delta文档模型,所有编辑操作(加粗、插入图片)都会生成Delta格式的JSON数据,举个例子,一段“Hello Quill”的内容,Delta长这样:
{ "ops": [{"insert": "Hello", "attributes": {"bold": true}}, {"insert": " Quill"}]}
这种结构化的数据,在Vue3里处理双向绑定、做版本控制(比如撤销重做)特别顺手,而且Quill暴露的API很直观,像quill.insertText()
、quill.getContents()
这些方法,哪怕是刚接触的前端同学,看文档也能快速上手。
可扩展性拉满,靠“模块”自由拼功能
Quill的Module机制是灵魂!想加代码高亮?装个quill-syntax
模块;要图片能拖拽缩放?用quill-image-resize-module
;甚至想做协同编辑,社区也有现成方案,Vue3的Composition API刚好能把这些模块的逻辑封装成可复用的函数(比如useQuillUpload
处理图片上传),项目里多处用富文本时,不用重复写代码。
轻量+生态,中小项目“性价比”首选
Quill核心包体积很小,只做基础富文本编辑完全够用,要是项目需要复杂功能,社区插件能补上——比如数学公式用quill-mathjax
、表格用quill-table
,对比TinyMCE(功能全但体积大,适合大型CMS)、Slate(高度灵活但需要自己造很多轮子),Quill在“快速落地需求”和“定制性”之间平衡得很好,中小项目选它不踩坑。
Vue3里咋最快把Quill用起来?
很多同学一上来就想“快速跑通流程”,这部分直接给三步落地法,哪怕是Vue3新手也能跟着做:
步骤1:装依赖,选对社区库
Vue3生态里,最常用的Quill封装库是vue3-quill
(注意不是老版本的vue-quill-editor
,那是Vue2的),命令行装包:
npm i vue3-quill
步骤2:封装基础富文本组件
用Vue3的<script setup>
语法,写个最简化的编辑器组件:
<template> <!-- QuillEditor是vue3-quill提供的组件 --> <QuillEditor v-model="content" :options="editorOptions" @ready="onEditorReady" /> </template> <script setup> import { ref } from 'vue' import { QuillEditor } from 'vue3-quill' // 引入组件 import 'vue3-quill/dist/style.css' // 引入默认样式 // 双向绑定的内容 const content = ref('') // 编辑器配置项 const editorOptions = { theme: 'snow', // snow是带工具栏的主题,bubble是气泡式(聚焦时显示工具栏) modules: { toolbar: [ // 配置默认显示哪些按钮 ['bold', 'italic', 'underline'], // 格式类 ['link', 'image'], // 媒体类 [{ list: 'ordered' }, { list: 'bullet' }] // 列表类 ] } } // 编辑器初始化完成后触发,能拿到Quill实例 const onEditorReady = (quill) => { console.log('Quill实例:', quill) } </script>
这段代码做了这些事:
- 用
v-model
双向绑定(content
变化时编辑器自动更新,编辑器内容变化时content
也自动更新); - 通过
options
配置主题和工具栏; - 监听
ready
事件,拿到Quill实例后能调用API(比如quill.insertEmbed()
插入图片)。
步骤3:验证效果,快速调试
启动Vue3项目(比如npm run dev
),打开页面就能看到带工具栏的富文本编辑器,试试点“加粗”按钮,输入内容是否变粗;点“图片”按钮,能不能插入图片(默认是base64格式,后面会讲改成服务器上传),如果没渲染出来,先检查这几点:
- 依赖是否装对(
package.json
里有vue3-quill
); - 样式是否引入(漏了
import 'vue3-quill/dist/style.css'
会导致工具栏样式乱掉); - Vue3的组件引入是否正确(别把
QuillEditor
导入路径写错)。
Quill工具栏能自定义到啥程度?咋改?
默认工具栏满足不了需求?比如想加“插入签名”按钮、把图片上传改成走后端接口、自定义字体选项……这部分教你从基础配置到深度自定义:
基础配置:用数组/对象改默认按钮
Quill的toolbar支持两种配置方式:
-
数组形式:适合快速开关功能,比如只保留加粗、链接:
modules: { toolbar: [['bold'], ['link']] }
-
对象形式:适合精细控制,比如标题只保留1、2级,隐藏3级及以上:
modules: { toolbar: { container: [ [{ header: [1, 2, false] }] // false表示“不显示标题”选项 ] } }
进阶:加自定义按钮(插入签名”)
想加个点击后插入“【个人签名】”的按钮,步骤分三步:
第一步:在toolbar里声明自定义按钮
给container
加一个自定义标识(比如'custom-sign'
):
modules: { toolbar: { container: [ ['bold', 'italic'], ['custom-sign'] // 新增自定义按钮 ] } }
第二步:给按钮绑定点击逻辑(handlers)
用handlers
配置按钮的点击事件,逻辑里调用Quill API插入内容:
modules: { toolbar: { container: [...], handlers: { 'custom-sign': function() { // this.quill 是当前Quill实例 const quill = this.quill // 获取光标位置,插入文本 const cursorPos = quill.getSelection().index quill.insertText(cursorPos, '【个人签名】', 'bold', true) } } } }
第三步:给按钮加样式(图标/文字)
Quill的工具栏按钮默认是文字,想改成图标?用CSS的伪元素:
/* 找到自定义按钮的类:.ql- + 自定义标识 */ .ql-custom-sign::before { content: '✒️'; /* 用Unicode图标,也可以用图片url */ font-size: 16px; }
高阶:改造图片上传(从base64到服务器上传)
Quill默认插入图片是转base64,要是项目里图片要存服务器,得重写图片处理逻辑:
第一步:拦截toolbar的“image”按钮事件
在handlers
里重写image
的逻辑,改成触发文件选择框:
modules: { toolbar: { container: [..., ['image']], handlers: { image: function() { // 创建隐藏的文件输入框 const input = document.createElement('input') input.type = 'file' input.accept = 'image/*' // 只允许图片 // 选择文件后触发上传 input.addEventListener('change', (e) => { const file = e.target.files[0] // 调后端上传接口(这里用axios举例) uploadImage(file).then(res => { const imgUrl = res.data.url // 假设接口返回图片URL // 插入图片到编辑器 this.quill.insertEmbed( this.quill.getSelection().index, 'image', imgUrl ) }) }) // 触发文件选择框 input.click() } } } }
第二步:处理粘贴图片(用户从剪贴板粘贴图片)
除了点击按钮上传,还要处理“粘贴图片”的场景,监听Quill的paste
事件,拦截粘贴内容:
// 在onEditorReady里绑定事件 const onEditorReady = (quill) => { quill.on('paste', (e) => { e.preventDefault() // 阻止默认粘贴行为(默认是转base64) const clipboardData = e.clipboardData || window.clipboardData const files = clipboardData.files if (files.length > 0 && files[0].type.startsWith('image/')) { // 同样调上传接口,把图片URL插入 uploadImage(files[0]).then(res => { quill.insertEmbed(quill.getSelection().index, 'image', res.data.url) }) } }) }
Quill在Vue3里咋拓展复杂功能?
项目需求一复杂,只靠默认功能肯定不够,这里举代码高亮、数学公式、版本控制三个典型场景,教你用Quill的模块机制拓展:
场景1:代码块高亮(结合PrismJS)
想让编辑器里的代码块自动高亮?步骤如下:
第一步:装PrismJS和样式
npm i prismjs
然后引入默认样式(也可以选其他主题):
import 'prismjs/themes/prism.css'
第二步:自定义高亮逻辑
写个函数,遍历编辑器里的代码块,用PrismJS高亮:
import Prism from 'prismjs' const highlightCode = (quill) => { // 找到所有代码块(Quill里代码块是<pre class="ql-syntax">) const codeBlocks = quill.root.querySelectorAll('pre.ql-syntax') codeBlocks.forEach(block => { const codeEl = block.querySelector('code') // 从class里提取语言(比如class="language-js" → 语言是js) const lang = codeEl.className.match(/language-(\w+)/)?.[1] || 'javascript' // 调用Prism高亮 Prism.highlightElement(codeEl, Prism.languages[lang]) }) }
第三步:绑定编辑器事件
在onEditorReady
里,监听text-change
变化时触发高亮):
const onEditorReady = (quill) => { quill.on('text-change', (delta, oldDelta, source) => { if (source === 'user') { // 只处理用户主动修改的情况 highlightCode(quill) } }) }
场景2:数学公式编辑(结合Katex)
想在富文本里插入数学公式($E=mc^2$$),用Katex渲染:
第一步:装Katex和Quill插件
npm i katex quill-mathjax
第二步:配置Quill的mathjax模块
在editorOptions
里加mathjax配置,让工具栏显示公式按钮:
import 'katex/dist/katex.css' import MathJax from 'quill-mathjax' const editorOptions = { modules: { mathjax: { config: (() => { // Katex配置,比如默认行内公式、块级公式 window.MathJax = { tex: { inlineMath: [['$', '$']], displayMath: [['$$', '$$']] } } return window.MathJax })(), editor: MathJax }, toolbar: [['mathjax']] // 工具栏加公式按钮 } }
点击“公式”按钮后,会弹出输入框,输入LaTeX语法(比如E=mc^2
),编辑器会自动渲染成公式。
场景3:版本控制(撤销/重做)
Quill内置history
模块,能记录内容变化,实现撤销(undo)、重做(redo),配置起来很简单:
const editorOptions = { modules: { history: { delay: 2000, // 每2秒记录一次历史(避免太频繁) maxStack: 50, // 最多存50步历史 userOnly: true // 只记录用户主动操作,忽略程序自动修改 }, toolbar: [['undo', 'redo']] // 工具栏加撤销/重做按钮 } }
要是想在Vue组件里自己加按钮控制撤销/重做,拿到Quill实例后调用API:
// 假设按钮的点击事件 const handleUndo = () => { quill.value.history.undo() // quill.value是保存的Quill实例 } const handleRedo = () => { quill.value.history.redo() }
Vue3下Quill性能咋优化?
多了(比如几千字+大量图片),容易出现卡顿,这部分给三个实战优化思路:
大数据量:防抖+Delta优化
当用户快速输入时,Quill的text-change
事件会频繁触发,导致性能下降,用防抖限制事件处理频率:
import { debounce } from 'lodash-es' const onEditorReady = (quill) => { // 防抖处理高亮/上传等逻辑 const debouncedHighlight = debounce(() => { highlightCode(quill) }, 300) // 300毫秒内只执行一次 quill.on('text-change', debouncedHighlight) }
尽量用Quill的Delta API做“增量更新”,而不是全量替换内容,比如要修改一段文字,用quill.updateContents(delta)
而不是quill.setContents()
。
模块懒加载:按需加载大依赖
像PrismJS、Katex这些库体积不小,要是页面初始化时就加载,会拖慢首屏,用动态导入(import())实现懒加载:
const onEditorReady = (quill) => { // 需要代码高亮时再加载Prism const loadPrism = async () => { const { default: Prism } = await import('prismjs') // 初始化高亮逻辑... } // 比如用户点击“代码块”按钮后加载 quill.getModule('toolbar').addHandler('code-block', () => { loadPrism().then(() => { // 加载完成后插入代码块 quill.insertText(quill.getSelection().index, '```js\n// 请输入代码\n```', 'code-block') }) }) }
组件销毁:及时清理Quill实例
Vue3组件销毁时,要是没销毁Quill实例,会导致内存泄漏(尤其是单页面应用,组件频繁切换时),在onUnmounted
钩子处理:
<script setup> import { onUnmounted, ref } from 'vue' import { QuillEditor } from 'vue3-quill' const quillInstance = ref(null) const onEditorReady = (quill) => { quillInstance.value = quill } onUnmounted(() => { if (quillInstance.value) { quillInstance.value.destroy() // 销毁Quill实例 quillInstance.value = null } }) </script>
集成Quill时那些“踩过的坑”咋解决?
实际开发中,样式乱、移动端适配差、粘贴内容脏这些问题很常见,分享四个高频坑的解决方案:
坑1:Quill样式和项目CSS冲突
Quill的snow
主题自带样式,和项目全局CSS(比如Element Plus、Ant Design Vue)容易冲突,解决方法:
-
用深度选择器覆盖:Vue3中用
::v-deep
(或>>>
)修改Quill的样式,比如把工具栏按钮改成圆角:::v-deep .ql-toolbar .ql-button { border-radius: 4px; }
-
自定义主题:如果冲突太多,直接复制Quill的默认样式文件,改成自己的类名,避免全局污染。
坑2:移动端工具栏按钮太小,点击难触发
手机上Quill的toolbar按钮默认尺寸小,用户点
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。