【quill.js】深入理解quilljs
栏目: JavaScript · 发布时间: 5年前
内容简介:这篇文章中的很多内容都来自Adam Charron的quilljs是一个现代富文本编辑器,它具备良好的兼容性及强大的可扩展性。用户可以非常方便地实现自定义功能。另一特点是,quilljs自带一套数据系统来支撑内容生产,Parchment是抽象的文档模型,是与DOM树相对应的树形结构。Parchment树由
这篇文章中的很多内容都来自Adam Charron的 《Getting to know QuillJS - Part 1》 中,文中结合了我自己的一些理解和经验,内容也做了些调整,希望能帮到准备使用quilljs的你。
quilljs是什么?
quilljs是一个现代富文本编辑器,它具备良好的兼容性及强大的可扩展性。用户可以非常方便地实现自定义功能。另一特点是,quilljs自带一套数据系统来支撑内容生产, Parchment 和 Delta 。
Parchment
Parchment是抽象的文档模型,是与DOM树相对应的树形结构。Parchment树由 Blot 组成,Blot即是DOM Node的对应物,Blot可能包含结构、样式、内容等。打个比方:用户在编辑器中输入了文字“你”,在Parchment树中就会产生一个TextBlot与之对应。
Delta
Delta是一个扁平的JSON数组,用于保存(描述)编辑器中的内容数据。Delta中的每一项代表了一次 操作 ,它的变化会直接影响到编辑器中内容的变化。下面这个delta就表示:
const delta = new Delta().retain(12) .delete(4) .insert('White', { color: '#fff' }); 复制代码
- retain(12) 表示保留编辑器中索引为0 - 12之间的blots;
- delete(4) 表示接上一个操作后,删除4个blots;
- insert('white', { color: '#fff' }) 表示接上一次操作后,插入文字'white', 并对其应用format 'color', #fff'
通过编辑器的方法 getContents
可以获取当前编辑器中的内容的delta数据:
Blot
Blot是Parchment文档的组成部分,它是quilljs中最重要的抽象。有了Blot,可以让用户对编辑器中的内容进行操作,而无需对DOM进行直接操作。每一种Blot都需要实现 blot接口规范 ,quill中的内置Blot都继承自 ShadowBlot 。
为了方便查找与blot相关的其他blot,所以每个blot都拥有以下这些引用属性:
-
.parent
—父级blot,包含当前blot。若当前blot是顶级blot,则为null
。 -
.prev
—上一个同级blot, 与当前blot拥有同一个parent, 若当前blot为第一个child,则为null
。 -
.next
—下一个同级blot, 与当前blot拥有同一个parent, 若当前blot为最后一个child,则为null
。 -
.scroll
—顶级blot,后面会提供更多关于scroll blot的信息。 -
.domNode
—当前blot的DOM结构,该blot在DOM树中的实际结构。
Blot生命周期
Blot主要通过调用 Patchment.create()
创建。Blot拥有几个生命周期方法,你可以通过使用同名方法去覆盖它们,并根据具体情况在你的逻辑代码中通过super去调用被你覆盖的方法,以保证blot的默认行为不被破坏。下面继续介绍这些生命周期方法:
Blot.create()
每一个Blot都有 static create()
函数,用于根据初始值创建DOM Node。这里也非常适合在node上设置一些与Blot实例无关的初始属性。该函数会返回新创建的DOM Node,但并未插入文档中。此时,Blot也还未实例化成功,因为Blot实例化需要依赖DOM Node。需要注意的是, create()
并不是任何时候都会在blot实例化前执行,例如:当用户通过 复制/粘贴 创建blot时,blot的创建会直接接受来自剪切板的HTML结构,从而跳过 create
方法。
import Block from "quill/blots/block"; class ClickableSpan extends Inline { // ... static create(initialValue) { // Allow the parent create function to give us a DOM Node // The DOM Node will be based on the provided tagName and className. // E.G. the Node is currently <code class="ClickableSpan">{initialValue}</code> const node = super.create(); // Set an attribute on the DOM Node. node.setAttribute("spellcheck", false); // Add an additional class node.classList.add("otherClass") // Returning <code class="ClickableSpan otherClass">{initialValue}</code> return node; } // ... } 复制代码
constructor(domNode)
Blot类的构造函数,通过domNode实例化blot。在这里可以做一些通常在class的构造函数中做的事情,比如:事件绑定,缓存引用等。
class ClickableSpan extends Inline { // ... constructor(domNode) { super(domNode); // Bind our click handler to the class. this.clickHandler = this.clickHandler.bind(this); domNode.addEventListener(this.clickHandler); } clickHandler(event) { console.log("ClickableSpan was clicked. Blot: ", this); } // ... } 复制代码
Blot注册
上面两段代码中,我们定义了一个简单的Blot,但此时还无法在quilljs中使用它,还需要进行注册,让Parchment认识我们的Blot。
import Quill from "quill"; // Our Blot from earlier class ClickableSpan extends Inline { /* ... */ } ClickableSpan.className = "ClickableSpan"; ClickableSpan.blotName = "ClickableSpan"; ClickableSpan.tagName = "span"; Quill.register(ClickableSpan); 复制代码
Blots需要通过唯一标识区分
一般来说,有两种方式来调用:
Parchment.create(blotName)
该方式为Blot实例的主要创建方式,通过传入已经注册的blotName来正确创建blot实例。通过 quill.update(Delta)
或者在逻辑中手动调用Patchment.create(blotName)均是此方式。
Parchment.create(domNode)
有时候我们需要通过传入 domNode
来创建blot实例,比如:粘贴/复制的时候,在这种情况中,Blots就需要用到 className 和 tagName 来区分。
在定义一个Blot时,需要为它指定 blotName 、 className 、 tagName 。
// Matches to <strong ...>...</strong> class Bold extends Inline {} Bold.tagName = "strong"; Bold.blotName = "bold"; // Matches to <em ...>...</em> class Italic extends Inline { static tagName = "em"; static blotName = "italic"; } Bold.tagName = "em"; Bold.blotName = "italic"; // Matches to <em class="italic-alt" ...>...</em> class AltItalic extends Inline {} AltItalic.tagName = "em"; AltItalic.blotName = "alt-italic"; AltItalic.className = "italic-alt" 复制代码
上面例子中,HTML结构中的 <strong></strong>
和 <em></em>
通过tagName区分生成对应的blot, <em></em>
和 <em class="italic-alt"></em>
通过className区分可以生成正确的blot。
Blot插入及挂载
经过了Blot的定义和创建的过程,我们还需要将创建好的blot实例插入到quill编辑器的文档树和HTML DOM树中。下面介绍两个api来完成Blot的插入及挂载:
newBlot.insertInto(parentBlot, refBlot)
这是最主要的插入方法,其他几个插入方法都是基于这个方式实现,该方法就是将 newBlot
插入到 parentBlot
的children中,默认作为最后一个元素插入,如果 refBlot 也正确传入了,就插入到 refBlot
前面。
parentBlot.insertBefore(newBlot, refBlot)
这个方法很常用,类似于 parentNode.appendChild(domNode)
,默认作为最后一个元素插入,如果 refBlot 正确传入,就插入 refBlot
之前。
注意:本文更关注quilljs底层的Patchment相关知识,在实际应用quilljs时,经常会通过构造 Delta 实例调用 quill.updateContents(Delta)
来改变编辑器内容。
Updates 和 Optimization
ScrollBlot
是最顶层的 ContainerBlot
,它包裹其余所有的blots,并且管理编辑器内的内容变化。 ScrollBlot
会创建一个MutationObserver,用于掌控编辑器的内容。
ScrollBlot
会追踪MutationRecords,然后调用 MutationRecord
的 target
中 domNode
对应的blot的 update
方法。相关的 MutationRecords 会被作为参数传入。接下来, ScrollBlot
会调用所有受影响的blot的 optimize
方法(包括这些blot的child blot)。
update(mutation: MutationRecord[], sharedContext: Object)
Blot发生变化时会被调用,参数mutation的target是blot.domNode。在同一次更新循环中,所有blots收到的sharedContext是相同的。
optimize(context: Object)
更新循环完成后会被调用,避免在 optimize 方法中改变document的length和value。该方法中很适合做一些降低document复杂度的事。
简单来说,文档的 delta
在 optimize 执行前后应该是一样的,没发生变化。否则,将引起性能损耗。
Delection 和 Detachment
remove()
该方法是最常用也最简单的完全移除 blot及其domNode 的方法。 remove
主要是将blot的domNode从DOM树中移除,并调用 detach()
。
removeChild(blot)
该方法只有 containerBlot及继承自containerBlot的类 具备,作用是从该 containerBlot 的 .children
中移除传入的 blot 。
deleteAt(index, length)
该方法会根据给定的 index
及 length
来移除调用者的children中对应的blot及内容,若index为 0
且length为调用者的children的length, 则移除自身。
detach()
解除一切blot与quill相关的引用关系,从blot的parent上移除自身,同时对children blot调用 detach()
。
结束语
到这里, Patchment 中Blot的主要生命周期已介绍完毕。quilljs的扩展性及其强大,几乎可以在quill编辑器中实现任何的定制化功能。通常的Block/Embed等Blot的定义都比较简单,容易理解,而相对复杂的应该是ContainerBlot如何应用。
后面讲专门写一篇文章,介绍**“如何使用Container创建嵌套结构的内容”**,有兴趣的朋友可以关注一下。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 【1】JavaScript 基础深入——数据类型深入理解与总结
- 深入理解java虚拟机(1) -- 理解HotSpot内存区域
- 深入理解 HTTPS
- 深入理解 HTTPS
- 深入理解 SecurityConfigurer
- 深入理解 HTTP 协议
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Go语言学习笔记
雨痕 / 电子工业出版社 / 2016-6 / 89
作为时下流行的一种系统编程语言,Go 简单易学,性能很好,且支持各类主流平台。已有大量项目采用 Go 编写,这其中就包括 Docker 等明星作品,其开发和执行效率早已被证明。本书经四年多逐步完善,内容覆盖了语言、运行时、性能优化、工具链等各层面知识。且内容经大量读者反馈和校对,没有明显的缺陷和错误。上卷细致解析了语言规范相关细节,便于读者深入理解语言相关功能的使用方法和注意事项。下卷则对运行时源......一起来看看 《Go语言学习笔记》 这本书的介绍吧!