富文本编辑器是什么?
一般地,可以对文字、图片等进行编辑的产品,称为富文本编辑器。 但随着日益增长的需求和日新月异的技术,现在的富文本编辑器已经不仅限于文字和图片,还包括视频、表格、代码块、思维导图、附件、公式、格式刷等等比较复杂的功能。
编辑器的开发历程
🎉目前,在富文本编辑器领域,对其具体的实现方法可以大致分为三种,分别是L0编辑器、L1编辑器和L2编辑器。他们的具体区别如下:
- L0:依赖
DOM
的contenteditable
属性,基于原生execCommand
或者自定义扩展的execCommand
去操作DOM
实现富文内容的修改。其输出为HTML
字符串。比如CKEditor1-4、UEditor、wangEditor。 - L1:对
DOM Tree
已经数据的修改操作进行了抽象,使开发者在大部分情况下,不是直接操作的DOM
完成的各种功能,而是使用L1框架构建的模型所提供的API完成的。比如Quill.js、ProseMirror、Draft.js、Slate。其输出为自封装的模型表示的数据。 - L2:不依赖浏览器的编辑能力,独立实现光标和排版。比如Goole Doc。这种方法成本较高。
execCommand存在的问题
🎉为什么会有L0~L2?答案肯定是L0有缺陷,或者有待优化的地方,才会继续发展。
-
富文本编辑的开发是一个很老的话题,但经久不衰。
-
L0阶段的富文本编辑器的开发,依靠浏览器提供的
contenteditable
以及execCommand
这两大原生的API可实现一个最简单的富文本编辑器:- 首先,将外部容器(一般为
div
)的contenteditable
属性设置为true
,该容器中就可以进行键盘、鼠标等操作了。 - 另,添加一个工具栏(toolbar),借助
execCommand
命令,实现对容器中内容的编辑功能,比如:文字加粗、斜体、下划线功能等。
- 首先,将外部容器(一般为
-
从理论上来说,以上操作就可以完成一个简易的富文本编辑器的开发。然而,
execCommand
存在一些问题:- 兼容性:
execCommand
命令依赖于浏览器,这就会涉及到各个浏览器对该命令的支持程度。MDN中已经明确表示这是一个已经废弃的命令。
- 黑盒子:
execCommand
命令是由浏览器执行了,就像一个黑盒子,我们只提供了输入(参数),具体的操作是不由我们自身控制,对于开发来说,这是非常不踏实的。execCommand
命令的返回值是布尔类型的值,表示该命令是否被正确执行。语法是如下:
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
返回值:布尔类型的值,表示该命令是否被正确执行。
参数 含义 aCommandName 命令名称,例如:blod(加粗)、backColor(背景颜色)等。 aShowDefaultUI 是否展示用户界面,一般为 false
。aValueArgument 额外的参数值,例如图片的 url
等。 - 兼容性:
-
行为不一致:对于同样的功能,
execCommand
命令会导致不一样的行为。比如,对于加粗功能,IE浏览器使用的是<strong>
标签 ,而其他浏览器是使用的是<b>
标签;还有其他各种迷惑行为...。这导致同一功能下,各个浏览器的DOM
可能不一致,后续可能会出现各种各样的妖魔鬼怪。 -
功能限制:
execCommand
命令并不是全能的,只能实现一些比较简单的功能,比如文字加粗、文字的背景色、颜色、字体、字号、斜体,标题、删除线、下划线等。并且其中字体只能1-7,标题只能是H1-H6。另外,对于稍微复杂一些的功能,比如表格、图片、代码块、附件等,该命令都不能直接实现。
解决问题
🎉 那既然 execCommand 命令
存在那么多问题,那我们能不能自己实现一个类似于这个的命令呢?使得行为、结构、表现都一致,并且过程还是可控的呢?当然是可以的,这称为自研 execCommand
。
- 在L0中,以 wangEditor 为例,在
execCommand
上包装了一层,对于execCommand
默认可以实现的功能,还是使用其本身,而对于不能实现的复杂功能,使用insertHTML
和insertElem
来实现。这种方法可以基本实现我们的需求,但是并没有对execCommand
本身做扩展,而且它还是一个黑盒,并没有从本质上解决问题。具体如下:
switch (name) {
case 'insertHTML':
this.insertHTML(value as string)
break
case 'insertElem':
this.insertElem(value as DomElement)
break
default:
// 默认 command
this.execCommand(name, value as string)
break
}
当我们在编辑器里面插入一些格式化的内容时,传统的做法是直接往编辑器里面插入相应的 DOM,通过比较 DOM 树来记录内容的改变。直接操作 DOM 的方式有很多不便,比如很难知道编辑器里面某些字符或者内容到底是什么格式,特别是对于自定义的富文本格式。那对于编辑器内容表示是不是可以想想办法让其内容表示的简洁并且准确,然后操作起来更方便呢?
🎉答案是肯定的,在 DOM
之上做一个抽象,即用非常简洁的 数据结构模型
来描述编辑器的内容和变化,类似于使用 VDOM描述DOM结构
,方便后续对 DOM
的增删改等一些列操作。
在L1中,大部分做法是:使用一个用来描述文档结构的数据模型(如 Delta),当内容改变时,也是用数据模型描述文档结构。然后使用自研的 execCommand
提供的 API
对视图进行更改。即:当内容改变时,先修改数据模型,然后修改视图。比如Quill.js、ProseMirror、Draft.js、Slate等。
🎉如果你对富文本编辑器的开发感兴趣,可以了解我们的开源团队:wangeditor。在这里有一群热爱编辑器开发的伙伴,我们时常通过分享和技术交流提高自己的技术,同时也一起做好 wangeditor 编辑器这个开源项目。我们都坚信:有价值的事情,未来一定会实现!