一、前言
想必大家看到这个题目,心中不禁会浮现几个问题:
- 什么是富文本编辑器?
- 富文本编辑器和游戏角色有什么关系?
- 为什么是降级 ing?
什么是富文本编辑器——富文本编辑器集成了格局设置、媒体嵌入、社交互动等一系列编辑性能,所见即所得的给用户提供多元的展现成果。譬如论坛、社区、评论等等都用到了富文本编辑器。
和游戏角色的关系——富文本编辑器和游戏角色有很多共通之处,为了让富文本编辑器的介绍更加有代入感,本文将采纳游戏角色类比的形式进行解说。至于共通之处体现在哪里,前面将一一介绍。
为什么是降级 ing——“降级 ing”代表继续的进行时,本文的目标是聚焦富文本编辑器的共性问题,抛砖引玉,心愿能给大家提供一些解决思路。富文本编辑器始终在继续倒退中,而对于共性问题的摸索也从未停歇过。
本篇文章次要分为五个局部:
- 前言
- 理解富文本编辑器
- 富文本编辑器选型指南
- 富文本编辑器如何扩大
- 总结
本文通过游戏角色类比的形式,心愿可能让富文本编辑器接触较少的开发者,都能够深刻的理解富文本编辑器。明天,咱们就一起来探讨下在富文本编辑器选型、扩大过程中遇到的共性问题。
二、理解富文本编辑器
通常,咱们在抉择一款新的游戏之前,都会抉择先去官网、论坛理解游戏材料,从中筛选出无效信息,辅助咱们抉择适合的角色。
开发人员在接到富文本编辑器需要时,也不会轻易抉择其中一个,而是基于宏大的数据进行技术选型。这一节内容,就是为后续的选型所做的筹备工作。
2.1 角色格调 – 富文本编辑器状态
游戏角色在开服上线前,都会默认装备不同的格调,则格调往往决定了咱们对于角色的初始印象。
富文本编辑器同样具备几种罕用的初始状态,经典模式、文档模式、内联模式,如下图所示:
那么从上图的比照中,能够看进去:富文本编辑器必不可少的组成部分是内容编辑区域。状态栏是用来记录编辑时的相干数据,能够暗藏。而工具栏则能够任意调整显示的地位、机会甚至切换至幕后操控(通过快捷键等形式触发)。
反之,咱们能够取得这样一条讯息:通过工具栏、内容区域、状态栏、菜单栏的不同组合能够赋予富文本编辑器不同的展现状态。
2.2 成长阶段 – 富文本编辑器倒退历程
游戏中的角色都是可成长角色,在成长过程总会呈现一些 瓶颈期,而跨过所谓的瓶颈期之后,角色的能力将呈现不言而喻的扭转。
在整个倒退过程中,富文本编辑器遇到过一些窘境。也正是因为这些窘境,能够将倒退历程分为 L0、L1、L2 和 L3 阶段。
L0->L1
L0,即初代的富文本编辑器,依赖于浏览器本身的 execCommand,仅提供了无限的命令,实现最简略的性能。随着对款式越来越丰盛的要求,此时的富文本编辑器无奈满足需要,L1 阶段的编辑器应运而生。L1 的富文本编辑器采纳 自定义 execCommand的计划,能够实现更加丰盛的富文本性能。
L1->L2
L0、L1 的富文本编辑器,依然都是通过 execCommand 批改 HTML。而不同浏览器中,对于同一表象的富文本,其 HTML 构造可能大不相同。
比如说 加粗,其 HTML 可能是 加粗 ,可能是 加粗 ,也可能是 加粗 等等。为了解决数据与视图无奈一一对应的问题,提出了 自定义数据模型 的概念。
自定义数据模型,是富文本编辑器在富文本 HTML-DOM 树的根底上抽离进去的数据结构,雷同的数据结构能够保障渲染的 HTML 也是雷同的。自定义的命令间接控制数据模型,最终保障渲染的 HTML 文档的一致性。
对于雷同的 HTML,不同的富文本编辑器最终出现的数据模型并不相同。以 Hello EditorName 为例,这里比照了 Quill、ProseMirror、Draft、Slate 的数据模型如下:
L2 阶段的富文本编辑器,通过抽离数据模型,解决了富文本中脏数据、简单性能难以实现的问题。通过数据驱动,能够更好的满足定制性能、跨端解析、在线合作等需要。
L2->L3
到 L2 阶段的编辑器,能够满足绝大部分的应用场景。那为什么前面又倒退出 L3 呢?
这是因为,L0-L2 的富文本编辑器都是基于浏览器的 contentEditable,在批改数据模型时,往往须要对用户操作进行拦挡。对用户行为进行拦挡是很难管制的,再加上不同浏览器的兼容问题,很容易呈现 bug。
为了解决 contentEditable 编辑不可控的问题,以 Google Docs 为代表的编辑器通过“自研排版引擎”步入了 L3 阶段。
自研排版引擎,彻底摈弃了 contentEditable,通过自行管制光标地位、选区绘制、排版、监听输出等行为,实现和浏览器类似的编辑成果。“自研”,无疑具备了更高的扩展性。但与此绝对应的,其开发难度高、老本高、隐性问题多,在整体体验和性能上与原生浏览器渲染仍存在肯定差距,该阶段的编辑器还有一段路要走。
ps: There are a thousand Hamlets in a thousand people’s eyes。
下述对于成长阶段的划分仅基于作者自己的认识。
回顾富文本编辑器的倒退历程,不难发现:富文本编辑器的构造脱离不了模型、视图、控制器这三大模块。如下图所示:
正如游戏角色所 冲破的瓶颈期,富文本编辑器在 L1 跃迁至 L2 产生的扭转是:自定义数据模型的抽离;L2 跃迁至 L3 的扭转则是:排版引擎的全新定义。
三、富文本编辑器选型指南
当咱们曾经通过各种渠道理解到游戏背景、人物材料之后,下一步就要登录游戏创立游戏角色。此时,老手经常遇到的困扰无疑就是:如何抉择最合适本人的游戏角色。
相似的,对首次接触富文本编辑器的小伙伴来说,常提到的问题是:我该抉择哪款富文本编辑器?
首先,能够依据你的业务需要,抉择对应阶段的富文本编辑器:
- 业务自身就是以富文本编辑器为外围,或者有协同编辑需要。—— 抉择 L2、L3 的编辑器扩大,或者自研编辑器。这里能够参考有道云笔记和语雀的计划,参考链接见文末。
- 业务需要频繁迭代,交互设计要求较高的。—— 倡议抉择 L2 的编辑器。
- 业务较为稳固,要求不高的。——L1、L2 任选。
- 如果业务场景比较复杂,难以评判之后的业务场景。——倡议抉择 L2 的编辑器。
其次,在选定好阶段的根底上,依据我的项目架构(Vue、React、Augular 等),以及富文本编辑器本身的特点,抉择适宜的编辑器就能够。能够从下述几个方面思考:
- 开源水平
- 社区生态
- 交互细节
- 扩大反对度
- 定制化老本
以上,就是我梳理的选型套路。像是 CKEditor、TinyMCE、Quill 等都是交口称誉的,大家在选型的时候无妨能够思考下这些编辑器:
四、富文本编辑器如何扩大
抉择适合的角色,仅仅只是游戏的开始。在游戏过程中,须要一直地调整游戏角色的技能树,将其后劲倒退到极限。
随着业务的一直倒退,对富文本编辑器也会提出更高的要求。对此,常常困扰开发人员的往往就是以下几个问题:
1、如何疾速的扩大富文本性能?
2、如何疾速的让编辑器面目全非?
对于以上两个问题,下文将从 能力扩大、主题革新 两个方面进行剖析。
4.1 能力扩大
本节内容不会聚焦某个富文本编辑器具体如何扩大,而是针对上述不同扩大形式分享一些通用的解决思路。
4.1.1 工具栏扩大
就像是游戏角色中,通过道具的不同拆卸计划,调整最终的战力数据。工具栏扩大就是通过对工具栏中不同性能的组合及革新,满足最终的业务需要。
常见的工具栏是由若干个性能按钮、状态按钮组、下拉菜单、模态框等组成,如下图所示:
个别的,富文本编辑器中都具备治理工具栏的配置项,可依据须要查阅官网文档。这里咱们探讨一种场景,如何对已有的性能按钮进行扩大?
以“Quill 编辑器字体高亮的性能”为例——该性能按钮的色彩与光标地位的字体色彩相响应,从而达到绑定变动的成果,如下图所示:
那么,如果我的项目中引入的富文本编辑器不提供这样的能力,该如何解决呢?这里提供了两种计划:
1)管制性能按钮原生的 UI 款式。
以下图 Tiny 的字体高亮性能为例,按钮是 svg 的构造,可通过管制 strokeColor/fillColor 达到成果。此时只须要在编辑器中减少光标地位变动的监听 OnSelectionChange,获取光标地位的字体高亮色彩,重置按钮 UI。
2)SVG 图标替换以后的按钮。
当性能按钮是通过图片的形式出现,很难管制 UI 变动时,就能够采纳此计划。以 SVG 图标替换图片图标,通过变更 svg-path 的 strokeColor/fillColor,达到雷同的成果。
小结:如果我的项目是首次引入富文本编辑器,这里无妨参考4.2 主题革新中计划二。
4.1.2 菜单栏扩大
“菜单栏扩大”相似是给游戏角色的配备减少一些辅助的技能,这些新增的能力依靠于配备,每个配备所装备的能力也互有差别。
本节所说的菜单栏,特指编辑器外部的内联菜单栏。比方图片工具栏、表格工具栏、右键菜单栏等。如下图所示:
对菜单栏来说,最常呈现的需要就是:给现有的插件新增菜单栏,如何实现呢?
1)富文本编辑器提供关联配置能力,间接依照API 文档配置即可。这里摘取了 Tiny 编辑器中局部菜单栏的配置计划,如下图所示:
2)不具备关联配置能力,此时须要监听光标地位的变动。当光标在对应富文本数据区域内变动时,触发事件 / 命令管制此菜单栏展现。
不论是以上哪种计划,扩大的菜单栏能够抉择内置到编辑器中实现,也能够通过事件抛出到编辑器内部,以自定义组件的模式关联。我比拟举荐应用自定义菜单栏组件的计划:
// 伪代码仅作为示例 辅助了解
// 富文本编辑器
<Editor :config="editorConfig"/>
// 自定义菜单栏组件
<ContextToolBarComponent @command="handleCommand"/>
editor.on("selectionChange",(selection)=>{
// 判断选区地位
CheckSelectionDataModel(selection)
// 管制菜单栏展现暗藏,绑定数据实体
ControlContextToolBarComponentShowHide(selectionPosition)
})
// 菜单性能触发
handleCommand(command, _instance){editor.execCommand(command, _instance)
}
4.1.3 编辑器外部扩大
对于游戏角色的战力晋升而言,道具和配备都属于外力上的增强,角色自身也是须要重点关注的。
在富文本编辑器倒退历程一节中,总结出 富文本编辑器的构造脱离不了模型、视图、控制器这三大模块,那么从这三个模块登程,扩大的计划也有所辨别。
数据模型扩大
之前的介绍中提到过,L1-L2 阶段变迁的要害行为是 抽离自定义数据模型。富文本编辑器的数据模型决定了最终富文本渲染的构造。当某个预置的富文本构造不能满足预期时,就须要对这个富文本的数据模型进行扩大。依据富文本编辑器是处于 L2 阶段前或阶段后,扩大形式也有较大区别。
以图片数据扩大关联图片备注为例,将
<figure>
<img src=“xxx”/>
</figure>
扩大为
<figure>
<img src=“xxx”/>
<caption> 图片备注 </caption>
</figure>
1)L2 阶段及之后 富文本编辑器已具备数据模型形象能力,此时只须要在数据结构中新增 / 编辑定义好的数据对象,并绑定渲染的 HTML 构造即可:
// 原数据结构 {type:'image',src:'xxx'}
// 扩大为 {type:'image',src:'xxx',caption:'图片备注'}
// 新增数据对象 caption:'图片备注',绑定 HTML 构造 `<caption> 图片备注 </caption>`
2)L2 阶段之前,富文本数据未进行数据模型的形象。针对这种场景,能够利用 html-parse-stringify 插件,自行抽离数据模型,再进行数据扩大。以 <p>hello HTML-parse-stringfy!</p> 为例,能够转化为下图所示的数据结构:
html-parse-stringify 插件,能够将 HTML_AST 化,从转化为所须要的数据结构,以后 html-parse-stringify 也有一些问题,本文中不做扩散,感兴趣的同学能够留言探讨。
视图扩大
视图应该比拟好了解,属性数据雷同的角色,装备不同的皮肤或者技能特效,在战斗过程中的出现成果不同。同一个富文本数据源,通过不同的视图扩大,就能够展现不同的视觉效果。
1)不扭转富文本的数据结构 , 仅在款式设置上有所辨别
通过切换 DOM 构造上绑定的 class 属性,切换不同的款式:
<blockquote class="pgc-blockquote-abstract"> 援用内容 </blockquote>
<blockquote> 援用内容 </blockquote>
<blockquote class="pgc-blockquote-quote"> 援用内容 </blockquote>
2)间接扭转富文本的数据结构
一般链接切换至卡片,数据结构由 Inline-Block 切换至 Block(link-card),DOM 渲染随之切换。DOM 构造比照如下:
<p><br></p><p><a href="http://www.vivo.com.cn">vivo 智能手机官方网站 -X60 系列丨业余影像旗舰 </a></p><p><br></p>
<p><br></p><a href="http://www.vivo.com.cn" data-draft-node="block" data-draft-type="link-card">vivo 智能手机官方网站 -X60 系列丨业余影像旗舰 </a><p><br></p>
控制器扩大
控制器是比拟形象的概念,对游戏角色来说次要是用来管制技能触发条件、开释的机会、触发条件及属性影响之类的。
相似的,富文本编辑器的控制器也是对数据层及视图层管制形式的统称。控制器的扩大,能够通过 事件、命令、配置项 等多维度实现。明天,咱们简略聊一下 事件和命令 如何扩大。
1)事件的扩大
事件有点像是被动技能,由角色被动开释。富文本编辑器会被动抛出一些事件,实现在编辑器外部或内部的管制,如 OnselectionChange、OnInit 等等。当新增的性能须要由编辑器外部管制内部组件,且原生的事件无奈满足时,往往须要通过新增事件监听的模式实现。
事件的扩大在跨端操作中十分有用,后续会在跨端实际一文中重点介绍。
// 简略举个例子,图片上传失败后往往须要触发从新上传:// 若图片通过编辑器上传,失败后点击从新上传是编辑器自带的行为逻辑。// 但放在客户端管制资源上传的场景下,便须要编辑器告诉客户端“某某资源从新申请上传”。// 这个时候的跨端通信,就须要富文本编辑器抛出事件告诉客户端执行操作。editor.on('appRetryingUploadImage', ({ data}) => {call('reUploadPic', { picUrl: data.path, fileId: data.id})
})
2)命令的扩大
命令管制与事件管制逻辑相同,命令相似被动技能,当外部环境达到某个条件时,触发角色的某种操作。富文本编辑器的命令治理就提供了在编辑器内部管制编辑器外部操作的能力。当操作不在 Commond 命令库时,就须要对 Command 命令进行扩大。不同编辑器对 Command 的扩大写法不同,然而万变不离其宗 —— Command 的外围是 exec、refresh。
以 CKEditor 与 Tiny 为例:CK5
class XXXCommand extends Command{refresh(){}
execute(){}
}
CK4
editor.addCommand('XXXCommand', {exec: ()=> {},
refresh: ()=>{}
})
Tiny
editor.addCommand('XXXCommand', () => {});
exec 为执行命令的回调函数,用来管制编辑器的相干操作的执行;refresh 为命令指行完结后的回调函数,罕用来管制命令执行后编辑器相干状态的刷新;
除事件、命令外,局部编辑器还能够通过扩大配置项等形式,达到定制化操作的目标。
4.1.4 新增富文本性能插件
要想将新技能的价值施展到最大,不仅须要将角色的属性数据晋升到适合的程度,还要灵便调配技能组,配置适合的道具配备等等。
富文本编辑器新增一插件,往往须要多个模块独特扩大:
开展介绍下上图中的各个模块:
定义数据模型
通过 4.1.3 数据模型扩大 一节,咱们能够发现:数据模型是新增富文本性能的外围。只有先确定好数据层,能力决定视图渲染如何管制,以及最终如何出现在前端。
定义数据模型,次要分三步走:
1、确定数据模型的 DOM 是以 Inline 类型、Block 类型还是可切换;
2、明确数据模型的准入限度及其可编辑限度,例如说题目中不能嵌套超链接等相似的规定;
3、确定数据模型及其数据输出、数据输入;
- 数据输出 即须要配置的内容,以图片为例,须要图片 URL、图片的备注文案
- 数据输入 为编辑器 HTML 渲染后的 DOM 构造
- 数据模型 包含:存储的 HTML 字符串、形象的自定义数据类型(JSON)
输出 - 模型 - 输入的转化示例图, 如下图所示:
自定义工具栏按钮
工具栏按钮是数据管制的窗口,能够外显在工具栏中,也能够暗藏通过快捷键管制。如果外显在工具栏中,须要依据具体需要,定制对应状态的性能按钮,绑定菜单或者管制操作,可参考 4.1.1 工具栏扩大 一节。
新增事件或命令
确定好数据外围和管制窗口之后,下一步就是制订控制策略。首先确定需要中的控制策略,是正向的——由富文本编辑器操作触发内部反馈,还是反向的——由内部触发编辑器外部操作,还是两者皆存在。而后依据控制策略,对应的抉择扩大事件、命令还是两者都扩大。具体扩大计划可参考 4.1.3 控制器扩大 一节
关联光标选区
通过光标的地位,确定以后选区对应的数据结构,从而管制非凡状态的切换。怎么确定是否须要关联光标选区呢?
1、新增性能的按钮状态是否与光标地位无关。在自定义工具栏按钮这一步骤中就能够实现关联;
2、新增性能是否须要关联菜单栏显示。解决计划参考 4.1.2 菜单栏扩大 一节;
3、新增性能是否与其余富文本性能相关联。如互斥逻辑 —— 题目内不容许插入超链接;
若确定须要关联光标选区,那么富文本编辑器中就须要减少 OnSelectionChange 的监听,实现相干的解决。
editor.on("selectionChange",(selection)=>{
// 判断选区地位
CheckSelectionDataModel(selection)
// 批改本身及其他按钮状态
ChangeButtonStatus(button)
// 管制菜单显隐
ControlMenuShow(menuBar)
})
关联操作记录治理(撤销、重做)
在富文本编辑器中进行交互操作时,不可避免的会呈现一些误操作。富文本编辑器的交互场景越简单,呈现误操作的概率越高。因而个别富文本编辑器都会对操作记录进行治理,用以升高误操作所带来的影响。
不同的富文本编辑器中 undo/redo 的解决逻辑不同,类似的是富文本编辑器会定义操作过程中的要害行为(如常见的插入、删除等),将其存储在操作记录中。
当咱们在新增的插件性能中关联操作记录治理时,只须要复用其余插件要害行为的入库出库逻辑就能够啦。
// 伪代码,仅辅助了解
UndoManage.push(keyOperation)
UndoManage.undo()
UndoManage.redo()
减少复制粘贴管制
“复制粘贴”算是富文本编辑器操作中最为头疼的问题之一,有相干开发教训的小伙伴们应该遇到过,从其余起源复制的内容粘贴到编辑器内,视图展现异样的状况。针对这种状况,往往须要对剪切板中的数据进行过滤,转化为富文本编辑器可辨认的数据。
editor.on('paste',(evt)=>{
// 依据光标处对应的数据结构,确定过滤规定
let filterRules = checkSelection()
// filterRulers JSON 数据结构对数据对象进行过滤批改
// filterRulers HTML 字符串能够应用正则表达式或者编辑器内置的过滤办法
evt.data = filterRulers.exec(evt.data)
})
4.2 主题革新
“主题革新”应该很好了解,就是游戏中的更换皮肤,疾速的切换游戏角色的格调。
在富文本编辑器中主题革新,其实也就是工具栏、菜单栏以及非凡富文本的款式上的更换。通常的解决计划有两种:
引入新主题款式文件。替换新主题款式文件,或者在旧主题款式上进行款式笼罩。
构建脱离于编辑器自身的工具栏组件。将主题批改波及到的工具栏、菜单栏脱离编辑器,在我的项目中创立全新的工具栏组件、菜单栏组件。
如果是对已有我的项目进行革新,那么须要思考到新旧主题切换的投入产出比,择优选取;如果是新我的项目且对主题款式细节要求较高的话,能够采纳计划二。
// 伪代码 仅辅助了解
<!-- 自定义工具栏 -->
<CustomToolbarComponent>
<ButtonBold @click="execCommondBold"/>
<ButtonUnderline @click="execCommondUnderline"/>
<ButtonHead @click="execCommondHead"/>
</CustomToolbarComponent>
<!-- 富文本编辑器编辑区域 -->
<EditorContainer></EditorContainer>
<script>
// 执行富文本编辑器的 Commond
execCommondBold:()=>{editor.execCommond('bold')
}
execCommondUnderline:()=>{editor.execCommond('underline')
}
execCommondHead:()=>{editor.execCommond('head')
}
</script>
<style>
// 自定义主题款式
button-bold{}
button-underline{}
button-head{}
</style>
计划二相较于计划一来说:
- 长处:将工具栏的控制权由第三方编辑器,迁徙至我的项目中,在可控性和扩展性都能失去最大限度的晋升;对跨端业务的适配度更高,各端只需一套管制计划,各性能组件分渠道定制即可;
- 毛病:须要将工具栏中按钮绑定的命令 / 事件、状态绑定等管制计划转移至新的组件中,会占用肯定的开发成本。
小结:性能扩大和主题革新的计划不止以上这些,也存在其余折衷的计划,只须要依据业务场景抉择适合的计划即可。有句话说的好:「最好的不肯定适宜,适宜的才是最好的」。
至此,本篇文章的内容也就靠近序幕了。心愿大家看到这里,对于以下几个问题,能失去一些解答:
1、基于当初的业务需要,该抉择哪款富文本编辑器?
2、随着业务的扩大,该如何扩大富文本性能?
3、设计改版,如何疾速的面目全非?
五、总结
就像是在游戏世界中,你不得不打怪降级。同时在过程中,你也会积攒更多的技巧,为之后的越级打怪打下松软的根底。在富文本编辑器开发过程中,的确会遇到很多难解的问题、简单的需要,破费了咱们大量的工夫精力。在一次又一次的锻炼中,咱们都将会有所播种,有所成长。
本篇文章分享了我在富文本编辑器开发过程中对于共性问题的一些思考,心愿能对行将参加富文本编辑器开发的小伙伴,或者正在进行富文本编辑器开发的小伙伴带来一些帮忙。
后续还会跟大家分享一些富文本编辑器在跨端计划解决上的一些教训,如果感兴趣的话能够继续关注。
参考资料
- 有道云笔记新版编辑器架构设计(上)
- 有道云笔记新版编辑器架构设计(下)
- 富⽂本编辑器的技术演进
- 开源富文本编辑器技术的演进(2020 1024)
- 从风行的编辑器架构聊聊富文本编辑器的窘境
- Quill Editor
- CKEditor
- TinyMCE
作者:vivo 互联网前端团队 -Tian Yuhan