【quill.js】深入理解quilljs
栏目: JavaScript · 发布时间: 6年前
内容简介:这篇文章中的很多内容都来自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 协议
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Chinese Authoritarianism in the Information Age
Routledge / 2018-2-13 / GBP 115.00
This book examines information and public opinion control by the authoritarian state in response to popular access to information and upgraded political communication channels among the citizens in co......一起来看看 《Chinese Authoritarianism in the Information Age》 这本书的介绍吧!