一、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前端网发表,如需转载,请注明页面地址。
code前端网



发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。