wangEditor - 富文本编辑器Level-1的探索(一)

2,234 阅读9分钟

可能对富文本编辑器不太了解的同学,对这篇文章会看的云里来雾里去的,也可能会对这类的文章压根没啥子兴趣。不过你可以尝试加入我们的wangEditor团队,需求促进学习~

这样一来你可能就会对这方面的东西感兴趣了,而且对于一个11.4k的开源项目你肯定能学到一些新的知识

文末附带联系方式^o^

为什么要做L1

既然要做,那就需要知道为什么要做,是出于什么样的动机去促使我们有这样的一个方向呢?

对于这个问题,不妨先从我们当前的编辑器功能来聊起。 目前我们的编辑器其实是完全依靠浏览器的能力的,也就是依靠着下面三个API来完成绝大部分的功能, 分别是:

  • contenteditable
  • execCommand
  • Selection

其实一个简单的富文本编辑器,依靠着这三个API就可以实现简单的编辑、加粗、改变文字颜色等功能。 而我们现在做的就是围绕这三个API去不断完善日益增长的用户需求。

那为什么我们要打破现状去搞什么L1呢?

这里先来解释一下什么叫做L1,L1即Level 1,简单来说就是不完全依靠浏览器的API来完成富文本编辑器的工作,而我们上面提到的完全依赖浏览器能力的阶段叫做Level 0

其实我觉得原因可以总结为两点:

  1. 用户群体日渐庞大,生产力日渐提高,导致当前版本的编辑器无法完全满足各种需求
  2. 当前浏览器的原生能力对于开发者来说是一个黑盒:不可见不可控,并且各个浏览器对功能的实现也不是很统一

这里重点围绕第2个因素进行描述。

contenteditable存在的问题

这个属性其实就是将某个元素设置为可编辑的状态,可以理解为将一个元素直接变成了Input框的形式,我们可以在里面进行输入文字等形式,只不过它是将我们输入的内容转化成了各种DOM结构来进行展示。如下图所示: 那这个属性存在什么问题呢?

  1. 每次换行的时候根据浏览器不同都会生成一个占位标签,chrome下默认生成的标签是div
  2. 在编辑区的图片可以随意拖拽,这就会造成打乱原有排版的问题
  3. 设置字体颜色后,换行后导致设置失效的问题(参考issue#2788

但是这些我们都可以在代码层面去进行调整(添加默认占位标签,禁止拖拽事件等),所以算不上什么大问题,关键的问题还是在于它允许用户编辑的这整个过程我们不可控,所以可能有时候出了问题我们也不太好定位。

不过整体看来,该属性在开发过程中好像没遇到什么特别棘手的问题,而且该属性的兼容性也还可以,总之该属性对本次L1的改造,不会有太大的影响,所以暂时不会抛弃该API的使用。

execCommand存在的问题

打开MDN官网,查看该属性,可以看到一个十分贴心并且醒目的标题 ^o^

当然目前在各大浏览器中还是可以使用的,但是保不齐哪天就暴毙掉了,所以这次的升级重心就是实现一套自己的execCommand操作

execCommand其实就是一个指令集,通过传入各种指令的名称来实现比如文字加粗,字体颜色,行高等功能。 这个方法的使用方式很简单,而且它的使用方式也不作为本文的重点,所以这里不做过多赘述,有兴趣的可以自行查看MDN官网

那我们直接来看下这个方法存在什么问题吧?

  1. 兼容性问题:从官网的提示就可以看出,面临废弃
  2. 行为不一致:相同的功能,各个浏览器的实现不同。比如加粗,有的浏览器使用b标签,有的浏览器使用strong标签
  3. dom结构紊乱: 有时候对一段文字进行加粗、颜色、斜体等操作,会使正常的dom结构变得面目全非
  4. 功能有限:比如改变字体大小的功能,就只能选择1-7这些尺寸

面对上面这些问题(尤其是第一条),所以我们需要做出迈向L1的这一步。

其实仔细想一下,就算execCommand不存在这些问题,对于我们开发者来说面对一个完全不可控的操作黑盒时也是十分头疼的,因为你完全不可预测它正在进行什么操作,会导致什么样的结果,相比之下其实我们更需要一个对于我们开发者来说一个更加透明的环境,所以我觉得自研execCommand是一个必然的趋势。

怎么做

以史为鉴,可以知兴替。 我们既然要做新的设计,那么就需要知道现在的设计存在什么问题,进而去完善我们的新设计。

当前架构

目前wangEditor的架构模型类似于MVC:

用户在编辑区的动作或者行为被捕获到之后,会根据对应的类型去执行不同的指令修改DOM结构,最后将DOM结构展现在编辑区。 eg: 加粗文字 ----> execCommand( 'bold') ---> 编辑区文字加粗展示

仔细分析一下,我们是没有严格意义上的Model层的,因为我们的数据模型采用的是浏览器的HTMLDocument,这就相当于每次的操作其实都是去直接修改DOM的,这样可能就会造成很大程度的性能浪费。

所以对于L1来说抽象出来一个数据模型是很有必要的。这个数据模型就是用来表示每次操作后存储的一个数据结果,根据这个结果可以渲染出来对应的UI层。

而我们需要自研的execCommand就是用来操作这个抽象出来的数据模型的。

未来的架构(初步想法)

目前的想法是这样的:

整个流程就是:

  1. 用户在编辑区的行为被捕获
  2. Controller根据不同的行为动作执行我们自研的command命令
  3. 执行命令之后输出一个操作结果,这个结果表示本次操作的节点信息
  4. 根据结果中表示的节点唯一信息,对比现有模型中的节点进行更新
  5. 最后将新的Model渲染至页面

eg: 用户选中文本加粗 ---> 执行weCommand({ type: 'bold', nodes:[ ... ], key: 'only' }) ---> 输出操作结果 {type: text, nodes:[], key} ---> 对比更新节点 ---> new Model ---> render UI

可以看到,目前的流程其实是一种单向数据流的体现**,**这里也是借鉴了redux的流程设计。 单向数据流可以很大程度保证我们数据的整洁,并且这样下来每个环节的职能单一,分工明确。

并且在新的架构中我们需要对两种数据格式进行设计和分析:

  1. 用户操作所对应的数据
  2. 最终渲染所需要的数据 (Model)

第一种数据的存在目的,是使其成为符合我们Command操作命令的格式 第二种的数据的话,就是一种类似dom tree的数据,方便我们渲染到页面中

为什么最后的数据要类似dom tree? wangEditor的核心理念就是轻量、易用,所以这次架构的升级,也不能对用户造成认知上的破坏性更新,这样无疑会增加用户的学习成本,所以要将结构尽量向我们熟悉的方向设计。

当然这些都是目前初步的调研想法,无论是各种操作的数据格式,还是最后生成的需要渲染的数据,这些都需要在之后对社区现存的优秀框架进行深入的分析之后再去确定。

这里也提出一些在架构设计过程中自己的一些思考和想法:

  • 如果不通过修改HTMLDocument的形式应该怎么去更新视图呢?
    • react、vue的虚拟dom ===> 真实dom
  • 用户在编辑区的输入需要进行实时监控吗?
    • No。那每次的数据模型是在用户进行操作时去获取并且处理吗?但是这样感觉缺少时效性
    • Yes。如果进行监控的话,是对编辑区进行input事件的监听吗?那这样是否对性能造成一些负担呢?
  • 怎么对操作数据和现有的渲染数据进行对比,并且以最小的性能为代价完成更新呢?
    • 类似diff算法,通过一种唯一标识去识别需要更新的节点?类似react框架中循环渲染所需要的 key
  • ... ...

总结

目前只是对当前富文本编辑器技术存在的一些问题,和现有富文本编辑器框架整体设计的一个调研,之后会带着上面提出的这些思考对一个优秀的富文本编辑器框架(目前暂定的是slate.js)进行针对性的深入研究。希望可以不断丰富自己这方面的认知吧~

本次的L1升级探索,其实是继wangEditor插件化改造之后的第二大工程了,而且是一块十分难啃的骨头。在每个环节的设计过程中其实还都需要考虑到目前正在进行的插件化工程,不能说因为做L1再去重构整个插件化的工作架构,只能是去尽力做到二者的完美兼容。

如果看完真的感兴趣的话可以加我的微信聊一聊: wx: liuqh233

参考资料