一、Vue3 项目里怎么快速引入 Quill 富文本编辑器?
做前端项目时,富文本编辑是很常见的需求,像博客发布、商品描述、后台配置这些场景都得用到,Vue3 项目里想快速搞懂 Quill 富文本编辑器的用法?这篇从基础集成到进阶扩展,把常见问题和实操步骤拆明白,新手也能跟着一步步搭起来~
想在 Vue3 里用 Quill,得先选对适配的封装库,现在社区里常用的是 vue-quill-editor-next
,它专门适配 Vue3 生态,能和 Composition API、Teleport 这些新特性友好配合,步骤分三步:
装依赖
打开终端,切到自己 Vue3 项目的根目录,执行安装命令:
npm install vue-quill-editor-next quill
这里要装两个包:vue-quill-editor-next
是 Vue3 组件封装层,quill
是富文本编辑器的核心库,缺一不可。
注册组件
可以全局注册,也可以局部在组件里引入,推荐局部引入,减少打包体积,比如在需要用的组件里:
<script setup> import { QuillEditor } from 'vue-quill-editor-next' import 'quill/dist/quill.snow.css' // 引入默认主题样式(snow主题,还有bubble主题可选) </script> <template> <QuillEditor v-model="content" /> </template> <script> export default { data() { return { content: '' // 初始内容,支持HTML字符串或Delta格式(Quill的结构化数据格式) } } } </script>
这里注意 v-model
绑定的 content
,默认是 HTML 格式的字符串,如果想改成 Delta 格式(更适合复杂操作和协作),可以加个 type="delta"
的 prop。
基础配置(可选)
刚引入的编辑器用的是默认配置,比如工具栏按钮、默认样式,如果想改基础配置,通过 options
prop 传参:
<QuillEditor v-model="content" :options="{ theme: 'snow', // 主题,snow是带工具栏,bubble是气泡式(选中文本才显示工具) placeholder: '请输入内容...', // 占位符 toolbar: [['bold', 'italic']] // 只保留加粗、斜体按钮,自定义工具栏 }" />
这样就能快速把 Quill 加到项目里,页面上会出现带基础格式的富文本编辑器~
怎么自定义 Quill 的工具栏?默认按钮不够用啊!
Quill 的工具栏是「可配置化」的,默认的按钮(比如加粗、列表)满足不了需求时,像加「格式刷」「清除格式」「自定义颜色选择器」这些,得自己改配置 + 写逻辑,分两步搞:
配置 toolbar 结构
Quill 的 toolbar 配置是个数组,每个子数组代表「一行工具」,数组元素是工具名称('bold'
)或自定义对象,举个例子,想加「清除格式」按钮:
<QuillEditor :options="{ toolbar: [ ['bold', 'italic', 'underline'], // 第一行:加粗、斜体、下划线 ['clean'] // 第二行:清除格式(Quill 内置的 clean 工具) ] }" />
但如果是完全自定义的按钮(插入我的名片」),得用对象形式配置,还要绑定点击事件:
<QuillEditor :options="{ toolbar: { container: [ ['bold'], [{ header: [1, 2, false] }], // 多级标题,false 代表普通文本 ['custom-button'] // 自定义按钮的标识 ], handlers: { // 给自定义按钮写点击逻辑 'custom-button': function() { // this 指向 Quill 实例 const quill = this // 比如插入一段自定义内容 quill.insertText(quill.getSelection().index, '我的名片:XXX', 'link', 'https://xxx.com') } } } }" />
这里 handlers
里的键要和 container
里的自定义按钮标识对应,函数里的 this
是 Quill 实例,能调它的 API(insertText
getSelection
)。
改工具栏样式
默认工具栏样式可能和项目 UI 不搭,得自己写 CSS,Quill 的工具栏 DOM 结构有固定类名,.ql-toolbar
是外层,.ql-formats
是每个工具组,可以直接覆盖样式:
/* 隐藏默认的边框 */ .ql-toolbar { border: none !important; } /* 自定义按钮 hover 样式 */ .ql-toolbar button:hover { background-color: #f0f0f0; }
要是用了 UI 库(Element Plus)的图标,还能把工具栏按钮换成图标:
<QuillEditor :options="{ toolbar: { container: [ [{ icon: 'Bold' }] // 假设用 Element Plus 的 Icon 组件,需要自己封装替换 ] } }" />
这种情况得自定义工具栏组件,把 Quill 的默认按钮换成 UI 库的图标,稍微复杂点,核心思路是「用自定义 DOM 代替默认工具栏,再通过 Quill API 触发格式变化」~
图片直接转base64太占空间,怎么改成上传到服务器?
Quill 默认把图片转成 base64 存到内容里,大图片会让内容体积爆炸,还不利于服务器存储,得改成「选图后上传到自己服务器,再把返回的 URL 插入编辑器」,步骤如下:
禁用默认图片处理逻辑
Quill 处理图片的逻辑在 image
模块里,先把默认逻辑关了:
<QuillEditor :options="{ modules: { image: false // 关闭默认image模块 } }" />
自定义图片上传逻辑
得自己监听「粘贴图片」或「点击工具栏图片按钮选图」的事件,然后上传,分两种场景:
场景1:点击工具栏按钮选图
先给工具栏加个「图片」按钮(自定义的),然后绑定点击事件打开文件选择框:
<QuillEditor :options="{ toolbar: { container: [['image']], handlers: { image: function() { // 触发文件选择框 const input = document.createElement('input') input.type = 'file' input.accept = 'image/*' input.onchange = (e) => { const file = e.target.files[0] if (file) { uploadImage(file) // 自己写的上传函数 } } input.click() } } } }" />
场景2:粘贴图片
Quill 支持监听 paste
事件,在组件里用 @paste
捕获:
<QuillEditor @paste="handlePaste" /> <script setup> function handlePaste(e) { const items = e.clipboardData.items for (let i = 0; i < items.length; i++) { if (items[i].type.startsWith('image/')) { const file = items[i].getAsFile() if (file) { uploadImage(file) e.preventDefault() // 阻止默认粘贴(否则会插入base64) } } } } </script>
实现上传函数 & 插入图片
uploadImage
函数里调后端接口,拿到图片 URL 后插入编辑器:
function uploadImage(file) { const formData = new FormData() formData.append('file', file) // 调自己的上传接口,假设返回 { data: { url: 'https://xxx.com/xxx.jpg' } } axios.post('/api/upload', formData).then(res => { const url = res.data.url // 获取 Quill 实例,插入图片 const quill = quillEditorRef.value.quill // 假设用ref获取组件实例 const selection = quill.getSelection() if (selection) { quill.insertEmbed(selection.index, 'image', url) quill.setSelection(selection.index + 1) // 光标移到图片后 } }) }
这里要注意:得用 ref
拿到 QuillEditor
组件实例,才能拿到内部的 Quill 核心对象(quillEditorRef.value.quill
),上传失败时要给用户提示,比如用 ElMessage
弹个错误~
想给代码块加高亮,Quill 能实现吗?
Quill 自带的代码块样式很朴素,就一个灰色背景,想实现像技术博客那样的语法高亮(JS、CSS 代码着色),得结合「语法高亮库 + 自定义 Quill 渲染逻辑」,推荐用 prism.js
,步骤如下:
装依赖 & 引入样式
先装 prismjs
和对应的语言包(prismjs/components/prism-javascript
):
npm install prismjs
然后在项目入口(main.js
)引入基础样式和语言:
import 'prismjs/themes/prism.css' // 基础语法高亮样式 import 'prismjs/components/prism-javascript' // JS 语法支持 import 'prismjs/components/prism-css' // CSS 语法支持
自定义 Quill 的代码块渲染
Quill 里的代码块是通过 code
格式渲染的,我们要重写它的渲染逻辑,用 Prism 高亮,需要自定义一个「模块」或者改写 Quill.import('formats/code')
:
import Quill from 'quill' import Prism from 'prismjs' // 改写默认的 code 格式渲染 const Code = Quill.import('formats/code') Code.prototype.domNode = function() { const node = super.domNode() // 给 code 标签加 prism 的高亮 class node.classList.add('language-javascript') // 假设默认高亮JS,也可以动态判断语言 Prism.highlightElement(node) // 触发Prism高亮 return node } Quill.register(Code, true)
但这样只能固定语言,实际场景中用户可能选不同语言(HTML、Python),所以更灵活的方式是「让用户选语言,再给代码块加对应 class」:
结合工具栏选语言
给工具栏加个「代码语言选择」的下拉框,选完后给代码块加 class:
<QuillEditor :options="{ toolbar: { container: [ ['code-block'], [{ code: ['javascript', 'css', 'html'] }] // 语言选择下拉 ] } }" />
然后监听代码块格式变化,动态加 Prism 的语言 class:
const quill = quillEditorRef.value.quill quill.on('text-change', (delta, oldDelta, source) => { if (source === 'user') { const codeBlocks = quill.root.querySelectorAll('pre.ql-syntax') codeBlocks.forEach(block => { const language = block.getAttribute('data-language') || 'javascript' block.classList.add(`language-${language}`) Prism.highlightElement(block) }) } })
这样用户插入代码块后,选语言,文本变化时触发 Prism 高亮,注意要给 Quill 的 pre
标签加 ql-syntax
类(默认代码块的类),确保样式不冲突~
Quill 和 Vue3 的表单怎么结合?比如和 ElForm 一起用?
Vue3 里用 UI 库(Element Plus)的表单时,Quill 作为表单项得处理「数据绑定 + 表单校验」,核心是把 Quill 的内容和表单的 model
绑定,步骤如下:
绑定 v-model 到表单项
假设用 Element Plus 的 ElForm
,Quill 组件作为 ElFormItem
的内容:
<template> <ElForm :model="form" label-width="120px"> <ElFormItem label="文章内容" prop="content"> <QuillEditor v-model="form.content" /> </ElFormItem> <ElFormItem> <ElButton type="primary" @click="handleSubmit">提交</ElButton> </ElFormItem> </ElForm> </template> <script setup> import { reactive } from 'vue' const form = reactive({ content: '' }) function handleSubmit() { // 这里可以调表单校验 // ElForm 会自动校验 prop="content" 的规则 } </script>
配置表单校验规则
如果要必填校验,给 ElFormItem
加 rules
:
<ElFormItem label="文章内容" prop="content" :rules="[{ required: true, message: '请填写内容', trigger: 'change' }]" > <QuillEditor v-model="form.content" @update:model-value="onContentChange" /> </ElFormItem> <script setup> const onContentChange = (val) => { // 触发表单校验(因为 ElForm 监听的是 form 的属性变化,Quill 的 v-model 变化会触发 form.content 变化,所以可能不需要额外处理) // 如果没触发,手动调 form.validateField('content') } </script>
处理复杂场景(比如富文本为空的判断)
Quill 的内容是 HTML 字符串,空内容可能是 '<p><br></p>'
这种(因为默认有个空行),所以表单校验时要处理这种情况:
const form = reactive({ content: '' }) const rules = { content: [ { validator: (rule, value, callback) => { // 去掉所有标签和空格,判断是否真有内容 const text = value.replace(/<[^>]+>/g, '').trim() if (text === '') { callback(new Error('请填写内容')) } else { callback() } }, trigger: 'change' } ] }
这样就能精准判断富文本是否真的有内容,避免空标签导致校验失效~
移动端用 Quill 编辑,怎么适配样式和交互?
Quill 默认是给 PC 端设计的,移动端(手机、平板)用的时候会有「工具栏按钮太小、键盘弹出顶布局、内容编辑区域滑动不流畅」这些问题,得针对性改样式和交互:
调整工具栏样式
移动端屏幕窄,工具栏按钮容易挤在一起,可以用媒体查询,让工具栏换行或变成滚动式:
@media (max-width: 768px) { .ql-toolbar { flex-wrap: wrap; // 让按钮自动换行 } .ql-formats { margin-bottom: 8px; // 每行之间留空隙 } }
要是按钮还是太多,改成「底部固定工具栏 + 折叠菜单」,比如把不常用的按钮放到下拉菜单里,点击后展开,这需要自定义工具栏结构,用 UI 库的 Popup
或 Dropdown
组件实现。
处理键盘弹出问题
手机输入时键盘弹出会把页面顶起来,导致编辑器位置偏移,可以固定编辑器高度,或禁止页面缩放:
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
然后给编辑器容器加固定高度,配合 overflow-y: auto
:
.ql-editor { height: 300px; // 固定高度,根据需求调整 overflow-y: auto; }
优化触摸交互
移动端触摸选中文本时,Quill 的气泡工具栏(bubble theme)可能显示异常,可以调整气泡工具栏的位置,或者强制用 snow 主题(带固定工具栏),禁止用户缩放页面(上面的 viewport 配置),避免双指缩放影响编辑。
简化功能
移动端场景下,用户很少用复杂格式(比如多级列表、代码块),可以把工具栏按钮精简,只保留「加粗、图片、链接、撤销」这些高频操作,减少用户学习成本~
回显时样式乱了,怎么解决?
富文本编辑完,把内容(HTML 字符串)放到页面上展示时,经常出现「字体不对、排版错乱、代码块没高亮」这些问题,核心原因是「回显容器没加载 Quill 的样式 + 自定义样式没同步」,解决步骤:
引入 Quill 核心样式
回显的 HTML 里包含 Quill
版权声明
本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。