官网:https://quilljs.com
quill可以是一个文本编辑器JS库或是文本编辑器构建库。它提供了结构化数据方式用脱离语言的方式描述编辑器内容,同时预置了内置插件,支持自定义插件,有助于在此基础上进行和业务相关编辑器开发。
准备工作 关于contenteditable
属性,selection
对象和range
对象的介绍,可以参考这篇文章 。
配置 1 2 3 const editor = new Quill ("#container" );const editor = new Quill (document .body );
quill通常使用上面的方式初始化。在初始化时,支持用丰富的配置项定义生成的编辑器。下面是一个例子。
1 2 3 4 5 6 7 8 9 10 const options = { debug : 'info' , modules : { toolbar : '#toolbar' }, placeholder : 'Tell a story...' , readOnly : false , them : 'snow' } const editor = new Quill ('#container' , options);
Quill支持的配置项有:
bounds
,Quill UI元素的限制范围,默认document.body
debug
,是Quill.debug
的快捷方式,用于打印调试信息,默认级别为warn
formats
,Quill中允许出现的格式,默认为所有格式 ,它和toolbar是解耦的
modules
,注册在Quill中的功能模块和与之对应的配置信息,Quill会有默认配置
placeholder
,提示信息
readOnly
,是否可写
scrollContainer
,编辑器滚动条的父级,默认为编辑器本身
strict
,版本更新配置,默认为true
theme
,整体外观,默认为snow
,bubble
可选
支持的文本格式 分为行内 ,块级 ,嵌入式 三大类。
行内:加粗/背景色/字体颜色/字体/行内代码/斜体/下划线/删除线/链接/字体大小/上、下标
块级:引用/标题/行首缩进/有序、无序列表/对齐/文本方向/代码块
嵌入式:音频/视频/公式
API 按照由浅入深,分为修改内容 ,修改格式 ,选取 ,编辑器本身 ,事件 ,数据模型操作 ,拓展 几大类。涉及到内容修改的都会返回代表更改的delta。
修改内容 涉及到修改时,最后一个参数都可以选择user
,api
,silent
。user
类型下,disabled时会没有效果。
deleteText
,输入起始点和长度,删除特定范围的内容,返回delta类型数据。如quill.deleteText(6, 4)
getContents
,获取delta格式的编辑器内容,如quill.getContents()
getLength
,获取文本长度,quill默认会有一个空行,所以默认返回1
getText
,获取文本内容,跳过非文本如音视频元素,如quill.getText(0, 10)
insertEmbed
,输入位置,类型,值,插入嵌入式内容,如quill.insertEmbed(10, 'image', 'https://quilljs.com/images/cloud.png')
insertText
,插入文本,可带格式。如下1 2 3 4 5 6 quill.insertText (0 , 'Hello' , 'bold' , true ); quill.insertText (5 , 'Quill' , { 'color' : '#ffff00' , 'italic' : true });
setContent
,输入delta,重设编辑器内容,需以\n
结尾。如下:1 2 3 4 5 quill.setContents ([ { insert : 'Hello ' }, { insert : 'World!' , attributes : { bold : true } }, { insert : '\n' } ]);
setText
,设置文本,返回代表改变的delta
updateContent
,输入delta,更新内容,返回代表更新的delta
修改格式
format
,设置用户当前所选的文本格式,如quill.format('color', 'red');
formatLine
,设置给定选择当前整行样式,使用类似format
的方法设置样式,也支持直接传入格式对象。类似quill.formatLine(1, 2, { 'align': 'right'})
formatText
,设置给定范围内文本格式,类似formatLine
getFormat
,获取给定范围内的格式,没有输入时,返回当前选择的格式
removeFormat
,移除范围内样式
选取
getBounds(index, length = 0)
,返回的top、width、height、left相对于编辑器容器而言
getSelection(focus = false)
,返回用户的选取范围,由index、length组成
setSelection(index, length = 0)
,设置选区范围,会自动focus,输入null会自动blur
编辑器本身
blur
,失焦
disable
,禁用
enable(enabled = false)
,启用
focus
,聚焦
hasFocus
,是否聚焦
update
,同步用户改动,协同工作时常用
事件 事件通过on
方法绑定在quill对象上。
text-change
,quill内的内容改变时触发,回调函数可以获取delta、oldContent,source。通常来自’user’,source
为’silent’时,该事件不会触发。
selection-change
,回调函数可以获取range,oldRange,source
editor-change
,上述两个事件触发时触发,即使source为silent
除了on
方法,还有once
用于绑定一次和off
方法解绑。
数据模型操作
find
,寻找DOM节点对应的quill或Blot对象
getIndex
,返回文档开头到给定Blot的偏移量
getLeafBlot
,返回给定位置的Blot
getLine
,返回给定位置整行的Blot
getLines
,返回给定范围的Blot
拓展
debug
,设置调试信息级别,info | log | warn | error
import
,导出quill相关库,输入相对于quill的路径1 2 3 4 5 var Parchment = Quill .import ('parchment' );var Delta = Quill .import ('delta' );var Toolbar = Quill .import ('modules/toolbar' );var Link = Quill .import ('formats/link' );
register
,注册module到quill中,有下面几种用法1 2 3 Quill .register (format : Attributor | BlotDefinintion , supressWarning : Boolean = false )Quill .register (path : String , def : any, supressWarning : Boolean = false )Quill .register (defs : { [String ]: any }, supressWarning : Boolean = false )
addContainer
,新增容器并返回
enable/disable
,启用、禁用编辑器
delta delta 是quill中最重要的概念。据介绍 所说,quill是“第一个”使用delta(结构化数据)这个概念的。不同于其他大多数文本编辑器需要反复执行修改编辑器中的HTML文档。quill维护一个delta数组,使用JSON数据的方式描述了文档的内容。
使用delta一词,并没有问题,因为可以理解成文档本身是由空内容 + delta一点点得到的。delta主要有两个特性:
权威性,delta和对应的生成结果是一一对应的,没有歧义
压缩性,delta中描述的操作是经过压缩后的
delta中的操作可以分为增、删、修改格式,分别对应insert
,delete
和retain
操作。对文本编辑器的一次改动(真实世界中的改动行为)只可能涉及上述三种行为的一种(Quill并不允许Ctrl多处选中)。其中retain的意义类似于光标的移动,它使得这三种操作并不需要使用index描述,便于Quill做优化和压缩。
delta的操作实际上是对parchment进行的,它类似于vdom,使用JS的数据结构对文本编辑器中可能出现的各元素进行了抽象,称作Blot。Blot有scroll,inline、block、text,break几种。父Blot下必须包含至少一个子Blot,而所有的Blot都包含在一个scroll Blot下。文本编辑器中特定格式的文本块都用特定的Blot表示,每个这样的Blot都必须继承自上面的一种Blot类型。就像通过下面的方式继承了Blot,就可以使对应的行内元素得到对应的编辑器样式元素对应起来,并使用在后面的编辑器里。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 let Inline = Quill .import ('blots/inline' );class BoldBlot extends Inline { }BoldBlot .blotName = 'bold' ;BoldBlot .tagName = 'strong' ;class ItalicBlot extends Inline { }ItalicBlot .blotName = 'italic' ;ItalicBlot .tagName = 'em' ;Quill .register (BoldBlot );Quill .register (ItalicBlot );quill.insertText (0 , 'Test' , { bold : true }); quill.formatText (0 , 4 , 'italic' , true );
类似地,我们定义一个Link Blot。它相比bold,italic不同的是,它需要一个string而不是boolean初始化。因此需要定义create
和format
两个函数。其中create在构造Blot时使用,value即输入的href,formats将用户的format字段和真实DOM的字段相关联。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 class LinkBlot extends Inline { static create (value ) { let node = super .create (); node.setAttribute ('href' , value); node.setAttribute ('target' , '_blank' ); return node; } static formats (node ) { return node.getAttribute ('href' ); } } LinkBlot .blotName = 'link' ;LinkBlot .tagName = 'a' ;Quill .register (LinkBlot );
定义引用这样的块级元素时,对应地继承Block Blot即可。和inline Blot不同的是,Block Blot无法嵌套,在对已有的块级元素应用时会替换而不是嵌套绑定在元素上。以Header元素为例,可以指定tagName为一个数组,可以在format时使用1、2的方式指定具体哪种tag。
1 2 3 4 5 6 7 8 9 class HeaderBlot extends Block { static formats (node ) { return HeaderBlot .tagName .indexOf (node.tagName ) + 1 ; } } HeaderBlot .blotName = 'header' ;HeaderBlot .tagName = ['H1' , 'H2' ];
类似的,可以在插入embed Blot,这种类型效果是插入在元素中间的新的tag。如Image。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 let BlockEmbed = Quill .import ('blots/block/embed' );class ImageBlot extends BlockEmbed { static create (value ) { let node = super .create (); node.setAttribute ('alt' , value.alt ); node.setAttribute ('src' , value.url ); return node; } static value (node ) { return { alt : node.getAttribute ('alt' ), url : node.getAttribute ('src' ) }; } } ImageBlot .blotName = 'image' ;ImageBlot .tagName = 'img' ;let range = quill.getSelection (true );quill.insertText (range.index , '\n' , Quill .sources .USER ); quill.insertEmbed (range.index + 1 , 'image' , { alt : 'Quill Cloud' , url : 'https://quilljs.com/0.20/assets/images/cloud.png' }, Quill .sources .USER ); quill.setSelection (range.index + 2 , Quill .sources .SILENT );
modules quill中的module位于quill的应用层。可以通过定制modules,利用quill的功能;或是更改quill内置module,修改quill本身的行为和功能。clipboard、keyboard、history三个module是quill默认加载的。用户完全可以根据业务需求定义自己的module。官网给了简单的例子 展示了module的大致骨架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 Quill .register ('modules/counter' , function (quill, options ) { var container = document .querySelector (options.container ); quill.on ('text-change' , function ( ) { var text = quill.getText (); if (options.unit === 'word' ) { container.innerText = text.split (/\s+/ ).length + ' words' ; } else { container.innerText = text.length + ' characters' ; } }); }); var quill = new Quill ('#editor' , { modules : { counter : { container : '#counter' , unit : 'word' } } });
只需要定义一个可以接收quill对象的函数即可,在函数内部利用quill事件监听即可完成应用层的建设。
Toolbar 和Clipboard 是Quill内置的两个module,对你构建自己的文本编辑器有很大的借鉴意义。
Toolbar用来定制工具栏上的按钮,是自定义编辑器(尤其是业务相关的编辑器)逃不开的一部分。它有几个基本配置:
container
,放置工具栏的DOM容器
handler
,点击ToolBar图标时注册的函数,传入Blot的value,通过调用Quill的API完成功能。也可以通过下面方式注册。
1 2 3 var toolbar = quill.getModule ('toolbar' );toolbar.addHandler ('image' , showImageUI);
在遇到从别的文本编辑器拷贝内容过来的情况时,需要修改ClipBoard Module中addMatcher
的定义。这个方法向ClipBoard中注册了新的Matcher匹配拷贝过来的HTML文本,将之转换为对应的Blot。如:
1 2 3 4 5 6 7 8 quill.clipboard .addMatcher (Node .TEXT_NODE , function (node, delta ) { return new Delta ().insert (node.data ); }); quill.clipboard .addMatcher ('.custom-class' , function (node, delta ) { return delta.compose (new Delta ().retain (delta.length (), { bold : true })); });
或者在configuration中,注入新定义的matcher即可。
1 2 3 4 5 6 7 8 9 10 var quill = new Quill ('#editor' , { modules : { clipboard : { matchers : [ ['B' , customMatcherA], [Node .TEXT_NODE , customMatcherB] ] } } });
参考