开发者社区> 初商> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

富文本编辑器的技术演进之路

简介: 如果你的业务也将面向国际市场,面向移动端设备访问,不要犹豫了,Hugo.js 就是你最好的选择!
+关注继续查看

image.png

作者:UC 国际研发 闻节


原生编辑器

浏览器提供了两个原生特性:
contenteditable:
https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Editable_content
document.execCommand():
https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
contenteditable 特性,可以指定某一容器变成可编辑器区域,即用户可以在容器内直接输入内容,或者删减内容。

execCommand API,可以对选中的某一段结构体,执行一个命令,譬如赋予黑体格式。

基于以上,可以做出最简单的富文本编辑器。

原来富文本编辑器是这么简单?当然不止如此简单!

首先问题集中在 execCommand() 身上:

*第一个是兼容性问题,第二个是能力局限问题。
*

传统编辑器

针对上述痛点,出现了第一代传统编辑器,他们主要的思路是:解决各种浏览器兼容差异,以及规避一些bug;同时对有限的命令集进行扩充。

其中具有代表性的包括:CKEditor(4-)、TinyMCE、UEditor、KindEditor、KissyEditor...

但这些耳熟能详的编辑器,还是会有许多问题:

  • 对浏览器差异的屏蔽,和bug的规避,成本巨大,而且不稳定,时不时发现一些新问题;
  • 对有限的命令集进行扩充,但不是基于execCommand() 进行扩展,而是自行封装实现效果,通过工具栏调用;
  • 只是能力扩充,但并没有提供通用扩展接口,开发者无法自定义一种符合业务需要的格式;

归结下来,最大的问题是:缺乏扩展性。

现代编辑器

虽有不足,但传统编辑器仍然被广泛使用,因为大部分业务都首先要解决从无到有的问题。大约到了2013年,开始出现一批现代编辑器(Modern Editor),他们有一个共同的特点:摒弃 execCommnand(),完全自实现各种格式、撤销、重做等功能,而且都是基于自建的数据模型,提供通用扩展接口。

其中具有代表性的包括:CKEditor 5、Slate.js、Quill.js、Draft.js、ProseMirror...

现代编辑器风风火火发展了几年,的确解决了传统编辑器的老大难问题:扩展性。基于现代编辑器的扩展接口,开发者可以自行定义格式,定义内容等,并且可以实现更复杂的编辑器内交互,使用户体验有所提升。

然而现代编辑也并非银弹,真正接入到业务系统后,会发现各种大小问题,而在深入使用后更会发现一个几乎无解的极大的挑战:不受控输入。

这首先要从现代编辑器的常见设计说起。正如上文所述,他们是基于自建数据模型的,即 model-base,即通过数据模型去描述整个编辑器的内部结构,而非html。如此对视图与数据做了抽象隔离,好处是,对编辑器内容的所有变更,实际上是抽象到对数据模型的调整,在完成调整后,再通过渲染引擎更新DOM,性能更优(更少触碰DOM)也更便于提供扩展接口。譬如,Quill.js 基于 delta 做数据模型;Slate.js 则背靠React,以state做数据模型; CKEditor 使用MVC模式,自建model层,等等。

虽然摒弃了execCommand(),但现代浏览器依然是基于contenteditable 特性,那么用户在容器中执行输入、删除等操作的时候,为了达到 model-drive-render 的效果,首先要通过事件捕获用户的行为,再执行API调整model,最后触发render。譬如,用户输入一个backsapce的时候,编辑器需要捕获住,然后通过API去执行删除行为,原生的backspace实际被截断了。这种强控制,除了是 model-base 驱动考虑外,还有一个显著好处,就是可以在一些特殊边界位置,做更多控制,譬如一些位置可以无效化退格删除等,诸如此类场景,有助提升用户体验。

那么问题来了,事实上存在一些场景,输入是不受控的,或者说是不可识别的,譬如,输入法。

输入法的实现,没有一个标准约束,不同的输入法都存在一些差异(浏览器差异的坑才填完,居然还有输入法这个大坑-_-!!),这些差异最重的一点就是,用户输入的内容,是否可以被识别。一旦出现输入不可识别,就会导致事件无法正确截取,最终流向了原生的行为,dom上的内容发生了变化,但是model的数据没有变化,产生的不一致会直接让编辑器的model-base机制崩坏。举个例子:

原本编辑器里面有“abc”三个字母,用户输入了不可识别的“退格”,直接删除了dom上的“c”,剩下“ab”,此时model里面依然是“abc”,如果通过编辑器model接口获取内容,就会拿到和预期不一的结果;

假设用户又输入了可识别的“回车”,事件被截获,然后通过API 调整 model,变成“abcn”,继而触发渲染,dom上从“ab”变成“abcn”。从用户感知上看,原本删除了的“c”,在回车后,又突然跑出来了...

这种不可识别、不受控的输入,在我们一般日常使用的中文输入法上并不常见,但一些小语种的输入法,或者是基于chrome extension 实现的输入法,以及安卓设备的输入法,都大量存在。而最可怕的是,我们可以检测到用户使用什么浏览器,给出提示,但却无法检测到用户使用什么输入法,完全无法防范,最终用户发现编辑的内容出现各种混乱。

不受控输入引起的混乱,并不是一个简单的bug,并不是修修补补就可以解决的问题,而是一个机制性的问题,是要从底层上重构去解决的根本性问题。

显然,如果编辑器所在业务,是要面向国际化用户,面向移动端用户的话,现代编辑器都不足以支撑。

新一代编辑器

早在2010年,Google Docs 团队由于对 contenteditable 特性不满,提出了一种新型的方案 ,他们连 contenteditable 特性都舍弃了,也不基于 execCommand,就是为了达到完全控制,不受浏览器差异影响。事实上,这种方案的实现复杂度相当高,因为原本浏览器帮你做了80%的事情,现在只剩20%了。为了达到最好的效果而不惜提高了复杂度,估计这也是 Google Docs 一直没有开源编辑器的原因吧。

上面提到的方案地址如下:
https://drive.googleblog.com/2010/05/whats-different-about-new-google-docs.html

可以看到,现代编辑器虽然都在 Google Docs之后产生的,但他们都没有采用 Google Docs 这种方案,他们保留了 contenteditable 就是为了控制复杂度。然而,面对不受控输入的挑战,我们最终发现,Google Docs 的方案才能真正有效解决。

纵观整个编辑器市场,不基于contenteditable 的,除了Google Docs,还有苹果的 iCloud Pages,并且更进一步将渲染层改成 SVG实现;后来网易的有道云笔记也实现了脱离 contenteditable 的编辑器。但这三个编辑器都是云服务的方式提供,并没有一个可复用可集成的开源编辑器。

Hugo.js 是阿里UC国际研发团队,参照 Google Docs 的方案抽象而成,第一个可解决不受控输入的可复用编辑器框架。经过抽象后的实现,我们称之为 shadow-input。为什么称为shadow ?下面一张图就能看明白:

image.png

如图,编辑器的编辑区域不再是一个 contenteditable容器,而是由三个层(layer)层叠而成,从上而下分别是 overlay-layer, render-layer, shadow-layer。

overlay-layer 负责模拟selection,即用户可见的光标、选中区间;

render-layer 负责渲染内容,即文本、图片等;

shadow-layer 负责承接用户输入,即各种输入法输入;

可见,三层layer实质上是将原来contenteditable容器的三种职责拆分了:

原来的光标,都是浏览器自带的,现在通过overlay模拟实现了;

原来的内容,都是直接可以在contenteditable 容器中编辑的,现在则强制通过 model-drive-render 方式更新了;

原来的不受控输入,都是直接落入contenteditable 容器中,现在则是重定向到了一个 shadow buffer中;

这里最重要的一点就是,我们将用户的输入重定向放到一个 shadow buffer 中,我们让用户的输入在一个不可见区域完整生效了之后,再去做内容检测,然后推断出用户的输入,以此来解决不可识别不受控的输入法输入。再举刚刚的例子:


原本编辑器里面有“abc”三个字母,shadow buffer 中也存有“abc”副本;

用户输入了不可识别的“退格”,退格并没有直接删除render-layer的内容,而是重定向落入了shadow buffer中,那么shadow buffer 的内容就变成了 “ab”,我们通过内容检查,可以推断出用户刚刚的输入,是一个退格删除行为,那么我们就可以调用 model.delete() API ,更新model并触发 render;

此时通过 model API 获取编辑器内容到时候,取到的是和dom表现一致的 “ab”;

假设用户又输入了“回车”,同样地通过shadow buffer 的内容检查,可以推断用户输入了回车,然后通过API 调整 model,变成“abn”,继而触发渲染,dom上从“ab”变成“abn”。dom视图和model数据始终保持一致,那么用户也不会见到突如其来的内容消失等混乱。


Hugo.js 通过 UC News 两印媒体人创作平台 Wemedia 落地实践,验证了这种方案在面向国际化用户,面向移动端用户的场景下,能提供更稳定的编辑体验,同时具备极强的通用扩展能力,可以应付业务的各种定制需求。

如果你的业务也将面向国际市场,面向移动端设备访问,不要犹豫了,Hugo.js 就是你最好的选择!(内外开源在路上...)

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
JSFIDDLE 助力 Threejs 功能探秘
JSFIDDLE 助力 WebGL 功能探秘 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循“署名-非商业用途-保持一致”创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS、Android、Html5、Arduino、pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作。
936 0
面向企业级前端应用的开发框架 UI5 的发展简史介绍
在移动应用已经无缝融合到我们日常生活的今日,我们的工作和生活几乎时时刻刻都在和 2C(即 To Customer) 应用打交道。比如手机支付,在线购物,生活缴费,天气和交通线路查询等等。
0 0
【云原生】 iVX 低代码开发 引入腾讯地图并在线预览
【云原生】 iVX 低代码开发 引入腾讯地图并在线预览
0 0
完美融入云原生的无代码平台 iVX编辑器介绍
完美融入云原生的无代码平台 iVX编辑器介绍
0 0
移动Web应用开发现状与未来
Web前端的起源 Web应用诞生:随着GMail、Google Map等优秀Web应用出现,Ajax在2004年之后一度成为热门话题。经过几年的发展,一批以Prototype、Dojo、Ext为首的Ajax+UI的浏览器兼容框架不断出现。
1274 0
【前端直播资料】GMTC2019 - 闲鱼基于 Flutter 技术的架构演进与创新
随着去年 Flutter beta 版本在端侧的可行性验证完成,今年团队进行了 Flutter 的架构全面升级和研发智能化的建设。在架构演进的过程中,产生了较多的技术创新和实践,本次 talk 将从以下几个方面进行重点分享:
10898 0
不止是动态化:Weex项目和阿里无线技术开源方向
阿里巴巴淘宝移动平台资深无线技术专家天施在杭州云栖大会期间分享了Weex项目介绍、起源与现状、Weex开源与社区,以及阿里移动技术开源。
3009 0
+关注
文章
问答
来源圈子
更多
由UC国际研发团队负责运营,我们将为大家提供与客户端、服务端、算法、测试、数据、前端等相关的高质量技术文章,不限于原创与翻译.
+ 订阅
文章排行榜
最热
最新
相关电子书
更多
去哪儿网qp热更新技术的架构与实践
立即下载
《优酷响应式布局技术全解析》
立即下载
Weex项目&阿里移动技术开源方向
立即下载