介绍
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 \n World! \n ' ); // 两行文本
quill. formatLine ( 1 , 2 , 'align' , 'right' ); // 第一行设置 align=right
quill. 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 ,会再分享文章。