vue3使用quill富文本编辑器,保姆级教程,富文本踩坑解决

'# vue3使用quill富文本编辑器,保姆级教程,富文本踩坑解决

一、背景与问题

富文本编辑器是现代Web应用中不可或缺的组件,尤其在内容管理系统(CMS)、在线协作平台等场景中。Quill作为一款基于Blot的富文本编辑器,以其模块化架构和强大的功能广受开发者喜爱。然而,在实际项目中,开发者常遇到以下问题:

  1. 初始化配置复杂:Quill的模块化设计导致配置选项繁多
  2. 内容与后端存储格式不匹配:Delta格式与HTML格式的转换问题
  3. 图片上传功能实现困难:需要处理跨域、格式转换、存储路径等问题
  4. 性能瓶颈:大规模内容渲染时的性能问题
  5. 安全风险:XSS攻击隐患

本文将深入解析Quill的工作原理,提供完整的代码示例,并解决常见坑点。

二、基本原理

1. Quill的架构设计

Quill采用Blot架构,通过Blot节点构建DOM结构。其核心概念包括:

  • Delta:表示内容的不可变数据结构(类似JSON格式)
  • Blot:DOM节点的抽象,分为:

    • LeafBlot:单字符节点(如TextBlot)
    • ContainerBlot:容器节点(如BlockBlot)
  • Modules:插件系统,支持自定义功能

2. 工作流程

  1. 初始化:创建Quill实例,加载基础模块
  2. 内容处理:通过Delta格式进行内容操作
  3. 渲染:将Delta转换为DOM节点
  4. 事件处理:监听用户交互事件

三、环境准备

1. 项目初始化

npm create vue@latest
cd quill-demo
npm install quill

2. 引入Quill样式

<template>
  <div id="app">
    <quill-editor 
      v-model="content" 
      :options="editorOptions"
    ></quill-editor>
  </div>
</template>

<script>
import { QuillEditor } from '@vueup/vue-quill'
import 'quill/dist/quill.snow.css'

export default {
  components: { QuillEditor },
  data() {
    return {
      content: '',
      editorOptions: {
        modules: {
          toolbar: [
            ['bold', 'italic', 'underline', 'strike'],
            ['blockquote', 'code-block'],
            [{'header': 1}, {'header': 2}],
            [{'list': 'ordered'}, {'list': 'bullet'}],
            [{'script': 'sub'}, {'script': 'super'}],
            [{'indent': '-1'}, {'indent': '+1'}],
            [{'direction': 'rtl'}],
            [{'size': ['small', false, 'large', 'huge']}],
            [{'header': [1, 2, 3, 4, 5, 6, false]]},
            [{'color': []}, {'background': []}],
            [{'font': []}],
            [{'align': []}],
            ['clean']
          ]
        }
      }
    }
  }
}
</script>

四、核心实现

1. 基础功能实现

<template>
  <div id="app">
    <quill-editor 
      v-model="content" 
      :options="editorOptions"
      @text-change="onTextChange"
      @blur="onBlur"
    ></quill-editor>
    <pre>{{ content }}</pre>
  </div>
</template>

<script>
import { QuillEditor } from '@vueup/vue-quill'
import 'quill/dist/quill.snow.css'

export default {
  components: { QuillEditor },
  data() {
    return {
      content: '',
      editorOptions: {
        modules: {
          toolbar: [
            ['bold', 'italic', 'underline', 'strike'],
            ['blockquote', 'code-block'],
            [{'header': 1}, {'header': 2}],
            [{'list': 'ordered'}, {'list': 'bullet'}],
            [{'script': 'sub'}, {'script': 'super'}],
            [{'indent': '-1'}, {'indent': '+1'}],
            [{'direction': 'rtl'}],
            [{'size': ['small', false, 'large', 'huge']}],
            [{'header': [1, 2, 3, 4, 5, 6, false]]},
            [{'color': []}, {'background': []}],
            [{'font': []}],
            [{'align': []}],
            ['clean']
          ]
        }
      }
    }
  },
  methods: {
    onTextChange(content) {
      console.log('内容变化:', content)
    },
    onBlur(content) {
      console.log('失去焦点:', content)
    }
  }
}
</script>

2. 图片上传功能实现

<template>
  <div id="app">
    <quill-editor 
      v-model="content" 
      :options="editorOptions"
      @image-change="onImageChange"
    ></quill-editor>
  </div>
</template>

<script>
import { QuillEditor } from '@vueup/vue-quill'
import 'quill/dist/quill.snow.css'

export default {
  components: { QuillEditor },
  data() {
    return {
      content: '',
      editorOptions: {
        modules: {
          toolbar: [
            ['bold', 'italic', 'underline', 'strike'],
            ['blockquote', 'code-block'],
            [{'header': 1}, {'header': 2}],
            [{'list': 'ordered'}, {'list': 'bullet'}],
            [{'script': 'sub'}, {'script': 'super'}],
            [{'indent': '-1'}, {'indent': '+1'}],
            [{'direction': 'rtl'}],
            [{'size': ['small', false, 'large', 'huge']}],
            [{'header': [1, 2, 3, 4, 5, 6, false]]},
            [{'color': []}, {'background': []}],
            [{'font': []}],
            [{'align': []}],
            ['clean']
          ],
          image: {
            sourceType: ['local', 'camera'],
            handlers: {
              'local': (blob) => this.uploadImage(blob)
            }
          }
        }
      }
    }
  },
  methods: {
    uploadImage(blob) {
      const formData = new FormData();
      formData.append('file', blob);
      
      // 模拟上传到服务器
      return fetch('https://api.example.com/upload', {
        method: 'POST',
        body: formData
      }).then(res => res.json()).then(data => {
        return data.url; // 返回图片URL
      });
    }
  }
}
</script>

3. 内容格式转换

// 将Delta格式转换为HTML
function deltaToHTML(delta) {
  const html = quill.formatToHTML(delta);
  console.log('Delta转HTML:', html);
  
  // 将HTML转为Delta
  const newDelta = quill.convertHtmlToDelta(html);
  console.log('HTML转Delta:', JSON.stringify(newDelta));
}

五、完整案例

1. 博客编辑器案例

项目结构

quill-demo/
├── public/
├── src/
│   ├── App.vue
│   ├── main.js
│   └── components/
│       └── BlogEditor.vue
├── package.json
└── .gitignore

BlogEditor.vue

<template>
  <div class="blog-editor">
    <quill-editor 
      v-model="content" 
      :options="editorOptions"
      @image-change="onImageChange"
      @text-change="onTextChange"
    ></quill-editor>
    <div class="controls">
      <button @click="saveContent">保存内容</button>
    </div>
    <div class="preview" v-html="previewContent"></div>
  </div>
</template>

<script>
import { QuillEditor } from '@vueup/vue-quill'
import 'quill/dist/quill.snow.css'

export default {
  components: { QuillEditor },
  data() {
    return {
      content: '',
      previewContent: '',
      editorOptions: {
        modules: {
          toolbar: [
            ['bold', 'italic', 'underline', 'strike'],
            ['blockquote', 'code-block'],
            [{'header': 1}, {'header': 2}],
            [{'list': 'ordered'}, {'list': 'bullet'}],
            [{'script': 'sub'}, {'script': 'super'}],
            [{'indent': '-1'}, {'indent': '+1'}],
            [{'direction': 'rtl'}],
            [{'size': ['small', false, 'large', 'huge']}],
            [{'header': [1, 2, 3, 4, 5, 6, false]]},
            [{'color': []}, {'background': []}],
            [{'font': []}],
            [{'align': []}],
            ['clean']
          ],
          image: {
            sourceType: ['local', 'camera'],
            handlers: {
              'local': (blob) => this.uploadImage(blob)
            }
          }
        }
      }
    }
  },
  methods: {
    onTextChange(content) {
      this.previewContent = this.formatToHTML(content);
    },
    onImageChange(imageUrl) {
      this.content = this.content + `<img src="${imageUrl}" />`;
    },
    uploadImage(blob) {
      const formData = new FormData();
      formData.append('file', blob);
      
      return fetch('https://api.example.com/upload', {
        method: 'POST',
        body: formData
      }).then(res => res.json()).then(data => {
        return data.url; // 返回图片URL
      });
    },
    saveContent() {
      // 调用后端接口保存内容
      console.log('保存内容:', this.content);
    },
    formatToHTML(delta) {
      return quill.formatToHTML(delta);
    }
  }
}
</script>

<style scoped>
.blog-editor {
  max-width: 800px;
  margin: 20px auto;
  padding: 20px;
  border: 1px solid #ccc;
}

.controls {
  margin: 20px 0;
}

.preview {
  margin-top: 20px;
  padding: 15px;
  background: #f5f5f5;
  border: 1px solid #ddd;
}
</style>

六、源码解析

1. QuillEditor组件源码

// @vueup/vue-quill 包的源码简化版
export default {
  name: 'QuillEditor',
  props: {
    value: {
      type: [String, Object],
      default: ''
    },
    options: {
      type: Object,
      default: () => ({
        modules: {
          toolbar: []
        }
      })
    }
  },
  data() {
    return {
      quill: null
    }
  },
  mounted() {
    this.quill = new Quill(this.$el, {
      modules: this.options.modules,
      theme: 'snow'
    });
    
    this.quill.on('text-change', (delta, source) => {
      this.$emit('text-change', this.quill.getContents())
    });
  },
  watch: {
    value(newVal) {
      if (this.quill) {
        this.quill.setContents(newVal)
      }
    }
  },
  beforeUnmount() {
    if (this.quill) {
      this.quill = null
    }
  }
}

2. Delta格式处理

// 转换Delta为HTML
function deltaToHTML(delta) {
  return quill.formatToHTML(delta);
}

// 转换HTML为Delta
function htmlToDelta(html) {
  return quill.convertHtmlToDelta(html);
}

七、进阶使用

1. 自定义模块开发

// 自定义模块示例
class CustomModule {
  constructor(quill) {
    this.quill = quill;
    this.addToolbarButton();
  }

  addToolbarButton() {
    const toolbar = this.quill.getModule('toolbar');
    toolbar.addHandler('custom', (range) => {
      this.quill.insertText(range, 'Custom Text');
    });
  }
}

2. 集成第三方服务

// 集成Markdown转换
import { marked } from 'marked';

function markdownToHTML(markdown) {
  return marked.parse(markdown);
}

八、性能与工程实践

1. 性能优化策略

  1. 虚拟滚动:使用quill-viewport插件实现长内容滚动优化
  2. 懒加载:对大段内容进行分块加载
  3. 事件节流:对频繁触发的事件进行节流处理
// 事件节流示例
function throttle(func, delay) {
  let timer = null;
  return (...args) => {
    if (!timer) {
      timer = setTimeout(() => {
        func.apply(this, args);
        timer = null;
      }, delay);
    }
  }
}

2. 安全防护

  1. XSS过滤:使用Sanitizer模块
// 配置Sanitizer模块
const sanitize = new Sanitizer({
  allowedTags: ['b', 'i', 'u', 'strike', 'blockquote', 'code-block', 'img'],
  allowedAttrs: {
    'img': ['src', 'alt']
  }
});
  1. 内容过滤:在保存前进行内容检查
function sanitizeContent(content) {
  return sanitize.sanitize(content);
}

九、常见问题与踩坑

1. 常见错误及解决办法

问题1:编辑器无法显示内容

// 错误代码
<quill-editor v-model="content"></quill-editor>

// 正确代码
<quill-editor v-model="content" :options="editorOptions"></quill-editor>

问题2:图片上传失败

// 错误代码
uploadImage(blob) {
  return fetch('https://api.example.com/upload', {
    method: 'POST',
    body: blob
  })
}

// 正确代码
uploadImage(blob) {
  const formData = new FormData();
  formData.append('file', blob);
  return fetch('https://api.example.com/upload', {
    method: 'POST',
    body: formData
  })
}

2. 性能瓶颈分析

问题:大段内容渲染卡顿

解决方案:

  1. 使用quill-viewport插件
  2. 对内容进行分页处理
  3. 使用虚拟滚动技术

十、最佳实践

1. 推荐方案

  1. 适合使用Quill的场景:

    • 需要复杂富文本格式的编辑场景
    • 项目需要模块化扩展能力
    • 需要支持图片、表格等复杂元素
  2. 不推荐使用Quill的场景:

    • 简单文本输入需求
    • 对性能要求极高的场景
    • 需要极简UI的场景

2. 推荐实践

  1. 使用Vue3的响应式系统:避免手动管理状态
  2. 配置Sanitizer模块:确保内容安全
  3. 使用TypeScript:增强类型安全
  4. 模块化开发:按功能拆分模块

十一、总结

Quill作为一款功能强大的富文本编辑器,在Vue3项目中具有广泛的应用场景。通过深入理解其Blot架构和Delta格式,可以更有效地进行开发和调试。在实际项目中,需要注意内容格式转换、安全防护和性能优化等问题。通过合理的模块化设计和性能优化策略,可以构建出高效稳定的富文本编辑功能。对于需要复杂编辑功能的项目,Quill是值得推荐的选择,但也要根据具体需求权衡利弊,选择最适合的解决方案。

VUE
最后修改于:2026年09月23日 20:59

评论已关闭

推荐阅读

AIGC实战——Transformer模型
2024年12月01日
Socket TCP 和 UDP 编程基础(Python)
2024年11月30日
python , tcp , udp
如何使用 ChatGPT 进行学术润色?你需要这些指令
2024年12月01日
AI
最新 Python 调用 OpenAi 详细教程实现问答、图像合成、图像理解、语音合成、语音识别(详细教程)
2024年11月24日
ChatGPT 和 DALL·E 2 配合生成故事绘本
2024年12月01日
omegaconf,一个超强的 Python 库!
2024年11月24日
【视觉AIGC识别】误差特征、人脸伪造检测、其他类型假图检测
2024年12月01日
[超级详细]如何在深度学习训练模型过程中使用 GPU 加速
2024年11月29日
Python 物理引擎pymunk最完整教程
2024年11月27日
MediaPipe 人体姿态与手指关键点检测教程
2024年11月27日
深入了解 Taipy:Python 打造 Web 应用的全面教程
2024年11月26日
基于Transformer的时间序列预测模型
2024年11月25日
Python在金融大数据分析中的AI应用(股价分析、量化交易)实战
2024年11月25日
AIGC Gradio系列学习教程之Components
2024年12月01日
Python3 `asyncio` — 异步 I/O,事件循环和并发工具
2024年11月30日
llama-factory SFT系列教程:大模型在自定义数据集 LoRA 训练与部署
2024年12月01日
Python 多线程和多进程用法
2024年11月24日
Python socket详解,全网最全教程
2024年11月27日
python之plot()和subplot()画图
2024年11月26日
理解 DALL·E 2、Stable Diffusion 和 Midjourney 工作原理
2024年12月01日