Quill 是一款非常优秀的 Web 富文本编辑器,近半年
npm
周下载量一直在 40w+ ,用户量很大。
Quill 的
官网
我有时候打不开,有时候打开很慢。于是,找到一篇中文翻译的
文档
和当前 npm 版本很接近。
从底层实现上,Quill 也和
slate.js
一样,是 L1 级 Web 编辑器,两者都是非常优秀的作品。只不过 slate.js 提供的是底层能力、供二次开发,而 Quill 提供的就是一个开箱即用的编辑器。
Quill 如此受欢迎,我觉得原因有:
-
简单易用,文档清晰
-
默认包含了常用的功能,下载即用
-
扩展性好,可自定义模块
-
发布时间比较久了,之前就积累了一大批用户
-
设计理念非常先进,model view 分离,支持 Operation transformation (可实现多人协同编辑)
不过看 Quill 的最近一次发布是 2019.9 ,离现在(2021.1)也挺久了。不知道以后还会不会频繁更新。
Quill 使用特别简单,几行代码即可生成一个编辑器。
<!-- Include stylesheet --><link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet"><!-- Create the editor container --><div id="editor">
<p>Hello World!</p>
<p>Some initial <strong>bold</strong> text</p>
<p><br></p></div><!-- Include the Quill library --><script src="https://cdn.quilljs.com/1.3.6/quill.js"></script><!-- Initialize Quill editor --><script>
var quill = new Quill('#editor', {theme: 'snow'// 其他配置项,可参考 https://quilljs.com/docs/configuration/
});</script>复制代码
配置工具栏
Quill 可非常灵活的配置工具栏,具体看
文档
。
定义工具栏 UI
可自定义工具栏 DOM 容器,并在初始化时传给 Quill 。也可以自定义菜单 UI ,但需要为 <button> 和 <select> 设置 ql-${format} 的 css class。
<!-- Create toolbar container --><div id="toolbar">
<!-- Add font size dropdown -->
<select class="ql-size"><option value="small"></option><!-- Note a missing, thus falsy value, is used to reset to default --><option selected></option><option value="large"></option><option value="huge"></option>
</select>
<!-- Add a bold button -->
<button class="ql-bold"></button>
<!-- Add subscript and superscript buttons -->
<button class="ql-script" value="sub"></button>
<button class="ql-script" value="super"></button></div><div id="editor"></div><!-- Initialize editor with toolbar --><script>
var quill = new Quill('#editor', {modules: { toolbar: '#toolbar'}
});</script>复制代码
分组自定义工具栏菜单
var toolbarOptions = ['bold', 'italic', 'underline', 'strike'];var quill = new Quill('#editor', { modules: {toolbar: toolbarOptions // 只显示用户需要的菜单
});复制代码
handlers
可以自定义 format 行为。
var toolbarOptions = { handlers: {// handlers object will be merged with default handlers object'link': function(value) { if (value) {var href = prompt('Enter the URL');this.quill.format('link', href);
} else {this.quill.format('link', false);
}var quill = new Quill('#editor', { modules: {toolbar: toolbarOptions
});复制代码
即我们常见的获取内容、设置内容、插入文字、删除文字等操作,具体可参考
文档
。
Delta 数据
和传统认知不一样,Quill 设置、获取的内容,都不是常见的 html 或者类似 vnode 的 JSON 数据,而是个 Delta 数据。
Delta 数据也是 JSON ,格式下文会详细介绍,这里先不必过于纠结。你只需要知道,Quill 可以通过 Delta 来表示内容,即可。
insertEmbed
插入图片、视频等卡片,和插入文本不一样,需要指定类型。目前 Quill 支持图片和视频两种,其他的可以自己扩展。
quill.insertEmbed(10, 'image', 'https://quilljs.com/images/cloud.png');复制代码
text-change
可以通过 text-change 及时获取修改的内容。
quill.on('text-change', function(delta, oldDelta, source) { if (source == 'api') {console.log("An API call triggered this change.");
} else if (source == 'user') {console.log("A user action triggered this change.");
});复制代码
对于富文本编辑器,选区是不可缺少的功能。特别是做插件扩展和二次开发,经常用到选区 API 。
Quill 的选区也是一个 Range 对象,格式非常简单。index 表示选区的开始位置,length 表示选区的长度。
可以通过 range-change 事件监听选区变化。这个也很有用,很多情况选区变了,UI 也需要随之变化的。
quill.on('selection-change', function(range, oldRange, source) { if (range) {if (range.length == 0) { console.log('User cursor is on', range.index);
} else { var text = quill.getText(range.index, range.length); console.log('User has highlighted', text);
} else {console.log('Cursor not in the editor');
});复制代码
最后,Quill 还支持获取选区的位置和尺寸。这就非常利于我们做诸如 @ 功能,以及在选区位置显示菜单,等功能。
富文本编辑器最基本的功能就是文本格式操作,例如加粗、斜体、颜色等。Quill 的格式操作非常清晰,可直接参考
formating 文档
,其中涉及到的 format 值有哪些,可参考
formats 文档
。
跟内容处理的 API 一样,格式操作返回的也是 Delta 数据,后面再解释。
// 1. 针对选中的文本,设置格式quill.format('color', 'red');
quill.format('align', 'right');// 2. 针对某个选区,设置格式quill.formatText(0, 5, 'bold', true);
quill.formatText(0, 5, { 'bold': false, 'color': 'rgb(0, 0, 255)'});// 3. 设置传入范围内所有行的格式quill.setText('Hello\nWorld!\n'); // 两行文本quill.formatLine(1, 2, 'align', 'right'); // 第一行设置 align=rightquill.formatLine(4, 4, 'align', 'center'); // 两行都设置 align=right// 4. 获取一个选区的格式quill.getFormat(1, 1); // { bold: true, italic: true }// 5. 移除传入区域所有格式和**嵌入对象**quill.setContents([
{ insert: 'Hello', { bold: true } },
{ insert: '\n', { align: 'center' } },
{ insert: { formula: 'x^2' } },
{ insert: '\n', { align: 'center' } },
{ insert: 'World', { italic: true }},
{ insert: '\n', { align: 'center' } }
quill.removeFormat(3, 7);// Editor contents are now// [// { insert: 'Hel', { bold: true } },// { insert: 'lo\n\nWo' },// { insert: 'rld', { italic: true }},// { insert: '\n', { align: 'center' } }// ]复制代码
module 模块
模块
就类似于插件,用于扩展 Quill 的能力。Quill 已经内置了
toolbar
clipboard
keyboard
syntax
和
history
,这些直接配置使用即可,具体去看文档。
var quill = new Quill('#editor', { modules: { toolbar: '#toolbar',history: { // Enable with custom configurations 'delay': 2500, 'userOnly': true},syntax: true // Enable with default configuration
});复制代码
可以使用
register
注册一个模块,然后使用
import
来引入。
register 也可以用于注册新的主题和格式,为了区分,用不同的前缀 modules/ formats/ themes/ 。
Quill.register({ 'formats/custom-format': CustomFormat, 'modules/custom-module-a': CustomModuleA, 'modules/custom-module-b': CustomModuleB,
});复制代码
也可以去继承和重写现有的模块。先用 import 引入,重写完,再 register 重新注册上、覆盖即可。
var Clipboard = Quill.import('modules/clipboard');var Delta = Quill.import('delta');class PlainClipboard extends Clipboard { convert(html = null) {if (typeof html === 'string') { this.container.innerHTML = html;
}let text = this.container.innerText;this.container.innerHTML = '';return new Delta().insert(text);
Quill.register('modules/clipboard', PlainClipboard, true);复制代码
Quill 将其核心概念,都单独抽离出来,分别写在不同的
github 仓库
,这个非常好。
Delta
想了解 Delta 的设计,提前得去了解一下
Operational Transformation
,即 OT 算法。这是目前多人在线协同编辑器的常用方案。Delta 就是参考 OT 算法来设计的,并不是自创的。
Delta 就是一种特定的 JSON 格式,用来描述内容的变化。所以,有了 Delta 数据,就能知道编辑器的内容。可以参考 Quill 文档中
Delta 的描述
。
如下图,通过阅读 Delta 内容,就能猜出它的意思。就是插入一行一行的文字,其中有换行,某些文字文字还有属性。
Delta 包含三种操作类型(也是 OT 算法里的),不可扩展,不可修改
-
insert 插入文字
-
delete 删除文字
-
retain 保留文字,或者理解为移动光标到某个文字处
insert
默认为插入文本,可以在插入时设置样式、属性。
{ ops: [
{ insert: 'Gandalf', attributes: { bold: true } },
{ insert: ' the ' },
{ insert: 'Grey', attributes: { color: '#cccccc' } }
}复制代码
当然,除了插入文字之外,还可以插入 embed 数据类型。此时 insert 属性值就不是字符串,而是对象。
{ insert: { image: 'xxx.png' }, attributes: { link: 'xxx.html' } }复制代码
还有,如何表示换行呢?普通的就用 \n,会被渲染为 <p> 标签。其他标签,可设置属性,如 { header: 1 } 。
{ ops: [
{ insert: 'The Two Towers' },
{ insert: '\n', attributes: { header: 1 } },
{ insert: 'Aragorn sped on up the hill.\n' }
}//表示的内容:<h1>The Two Towers</h1><p>Aragorn sped on up the hill.</p>复制代码
delete
删除指定长度的文本。如下图,先试用 retain 将选区定位到 30 的位置,然后删除接下来的 5 个字符。
retain
retain 稍微难理解一些。retain 字面意思是“保留”,如 { retain: 10 } 意思就是保留当前选区之后的 10 个字符。其实它的意思就是:移动光标到 10 个字符之后,然后继续做其他操作。这样就比较好理解了。
例如上文的删除操作,是移动光标到 30 个字符串之后,然后再删除掉接下来的 5 个字符。
retain 还有一个重要用途,就是修改属性。如下图,先移动光标到 6 个字符之后,然后对接下来的 5 个字符,设置 color 属性。
Parchment Blot
Parchment is Quill's document model. It is a parallel tree structure to the DOM tree, and provides functionality useful for content editors, like Quill. A Parchment tree is made up of Blots, which mirror a DOM node counterpart.
Parchment
和 Blot 就是 Quill 对于 DOM 节点和操作的模拟。Blot 就相当于 Node,但它包含了很多 Quill 富文本操作需要的 API ,这些是原生 DOM API 没有的。
Quill 提供了一些和 Blot 相关的
API
,不过都是写实验性质的。
// 静态方法,给定一个DOM节点,返回对应的 Quill 或 Blot 实例Quill.find(someNode)// 给一个 offset (从文档开始位置计算),返回当前选区定位的 blot 和 offset(当前 blot 开始位置计算)const [blot, offset] = quill.getLine(17); // [Block, 2]// 返回文档开始至给定 blot 位置的距离quill.getIndex(blot) // 15// getLeaf 根据 offset (文档开始位置),返回叶子节点。// 叶子节点可能有很多分类,例如 TextNode Image Video 等,请看下图。// 返回选区范围内的所有行的 blot 对象(Block 类型)quill.getLines(2, 13) // [Block, Block]复制代码
Quill 是一个很强大、很易用、设计理念很先进的编辑器。它的模块机制,渲染机制,Delta 操作,很值得我们学习和研究。
本文对 Quill 的使用和概念,做了一个简单的介绍,并未深入。未来我会继续研究 Quill ,会再分享文章。