文档技术连载 1/4:钉钉文档编辑器的前世今生
前端早早聊大会,前端生长的新起点,与掘金联合举行。
加微信 codingdreamer 进大会前端技能沟通群。
第十二届|前端可视化专场,7-18 直播,10 位讲师(蚂蚁金服/阿里云等),大会报名地址
本文是第九届前端在线文档技能,前端早早聊第 60 场,来自钉钉文档团队高级前端技能专家 – 展新的同享讲稿扼要收拾版(r N % P v 4 . 7 G完好版含演示请看录播视频和 PPT):
我们好,我是展新,来自钉钉文档团队。2011 年参o 6 z s ? _ D =加支付宝,一路成善于支付宝的前端团队,孵化了语雀,2018 年到钉钉,敞开钉钉文档的旅程。
今日首要和我们从其他一个视点来同享,一起来知道修正器,解说钉钉文档修正器的宿世此生,让更多对修a % J 0 G 0 = c Z改器感兴趣的人能更好地了解这一个领域。
一、Web 修正器简史
在和我们同享修正器技能之前,想a K 4 ~和我们来介绍下 Web 修正器的简史,由于在修$ d m X A j [改器这个领域H c D _ M 9 Z L,作为人机交互最凌乱的场景之一,我们了解它的“宿世”,可以更清晰找到修正器的现在一些规划的根源。

打字机时代
修正器满足的m % c g初步需求,就是输入(Input)和输出(Output),我们在, s 2 x + ?人机交互的极简版别时期,可以从打字机上找到它的一些特/ . W u J 7 8 p色,而这些特7 ) C k | 5 7 $色,直接影响且承继至今。
键盘操作
在打字机上,键盘S B Z D – Y的排布和我们现在用的键盘几乎差不多。实践上我们在修正输入时分,除了 26 个N F 5 ,字母之外,常* a 3 L 4 x q用的“Enter”、“Shift”、“Space”等实体按键,以及他们点按之后交互反响一起沿用至今。这些交互反响,可以用一个比较实践的测试用例描绘来标识,例如:
按下“Enter”,修正器会“换行”
— 从最早的打字机上,我们就可以体验到修正器的最基础的功用了。”
光标
打A k v O j字机有一个很显着的特征,就是打字的l ` g ( 时分,在纸上会有一个游标在右移。在打字机时代这v O D [ = d w k o个游标,就成为我们后来的光标了。它用于定位,可以在那里落笔。
WYSIWYG
当我们在打字机上敲下一个按键的时分,白纸上的游标方位处,就会输出黑色的字符。这是一个所见即% G k L & 7 : ^所得的进程。而现在许多的修正器,也正是为了满足所见即所得的产品。
文本修正器时代

跟着核算机的广泛和演进,我们可以看到在屏幕前进行文本修正成为最基础的诉求。而文本修正器时代,它和打字机时代最大的差异在于下面几点:
-
查找替换:可以依据修正器的内容,进行批量的“查找”和“替换” -
CCP:内容之间的输入,不S _ Q r t ? V v在局限于字符的一个个输入,还支撑了仿制、粘贴、仿制 -
Undo & Redo:当你输入之后,你可以反悔,还能反悔你的反悔 — undo & redo -
Syntax highlighting:一起,文本修正器时代,你输入的文本$ 5 i v ~ ! S G }样式,就不再局限于白纸黑字,部分的内容还可以支撑不同的色彩(高亮)
富文本修正器时代

富文本修正器,丰盛在格式。我们可以看到它和纯文本之间,又多了一些特性:
-
支撑最基础的文本格式(加粗3 j d c、斜体、i Q m # : o I 9 .删去线…) -
支撑依据整个阶段的样式H ! p . V h – A设置(布景色彩、字体大小…) -
支撑非文本的类型(图片、音视频、代码块…)
而正是越来越多丰盛的格式,添加了在不同终3 { q t F k端上结束 WYSIWYG 的难度。要想保持在不同的前语之前输入的内容,可以一起的输出(特别是电子屏到纸质的打印),富文本时代无疑添加O x _ A =了不少门槛。
Web 修正器时代
怎样定义 Web 修正器呢?最基本的特征就是依据阅读[ 8 ? / % l D /器的修正器。在阅读器里编写文字,我们最常用l l , Y的就是运用 Tex– ! | K f y [ q Ctarea 或许 InJ | M S e ~ 5 S |put 了,但编写富} = D L文本内容的话,则需求依靠富文本修正器的才华。所以,Web 修正器时代,还有一个特征,就是修正器输出的内容,往往会和 HTML 的结构性匹配,以& I C d %便于在阅读器端去消费输出的数据。
Web 修正器时代和我们的联络最为接近d c R S z,例如这篇文档,就是在语雀的修正器中修正操作结束。那么回想下整个富文本修正器的前史,我们可以大约划分为几个小阶段:
阶段一
在这个阶段中,有Z . + ! + u K不少优异的修正器萌发和得到推行:
这一个阶段的开源修正器,依靠阅读器特_ { | a 4 I ^性,首要是运用到了 designMode、Con5 ~ D B J J v )tentEditable、webkit-user-modify、**execCommand **等特性。前期的修正器都选用 | k , 2 s ) /这种方案,但可定制的空间有限。
阶段二
这一个阶段的修正器,部分运用阅读器的特性、D{ 7 ,OM 的 API 来自主结束 Selection、Range、Element、**T= ! b = p 4 2 P BextNodv ) % J – z Ae **等,具有必定的可扩展性,但也会0 ? 0 % t , R有许多难以处理的问题。
二、Web 修正器时代之难w T % : – p `
当我们依据阅读器来规划和开发修正器的时分,实践上是凌驾于修正器去开发一个高阶的输入组件。本应该由 HTML 规范来定义的富文本修正才华,却在不同的阅读器厂商中有不同的结束办法。规范化一贯比较模糊,是导致富文本修正器成为天坑的原因之一。
当然这个原因,许多人现已在 www.zhihu.com/question/38… 中有所耳闻。这儿Z K m a { ) : , s也不再细说。我们且从所, f o Z , ? 0 b P见即所得谈起。
WYSIWYG

这是P : R = I来自 **Wikipedia **的介绍,我们抽出其间的要害词:cZ 8 d S C { { gontent(text and graphics)以及 printed、dZ | ^ Y U wisplayed 来看下,富文本修正器的价值在于可以输入内容且满足在不同终端下的显现一起性。
三个定理
而为了满m B ( _ ( = s |意这个要求,怎M X t么才华做到 WYSIWYG 呢?

即:
-
DOM 内容和可视化(Visible)内容可以很好地进行映射。 -
DOM 选择和可视化(Visible)选择可以很好地进行映射。 -
一切的可视化修正都可以映射到一个从代数上来说封闭的和完好的可视化内容调集上面。
DOM 是我们能在 HTML 中表述的一切网页页面的调集,而修正器要处理的行为和 DOM 之间的联络在于可以让用户输入的内容,正确地在 DOM 中标明出来;用户选中的区域,也能在 DOM 中显现出来。在= Z E $ /这个进程中,; M , P } f q修正器一般会有一个内存模型的概念,用于处理用户行为的收拾,再驱动j } h z & $ y DOM 的更新。
ContentEditable is Terrible
我们在结束所见即所得的修正器的时分? $ U U O h 1 y,除了运用** Canvas** 来完全制造之外,还有其他一种方案就是运用 **ContentEditable **来结束内容的可修正。但这个特色,并不是那么友爱。首要表现在两个方面:
其一,HTML 本身就是非常自在嵌套组合的,用于描绘一个样式“粗体斜体”,会有许多种不同的表达办法。例如要结束下) ) A面的这一句话:

HTML 的嵌套联络; 7 s E { j | 4 )将会有多种结束方案,大致如下:
<strong>&l3 | F S m | Qt;em>Baggins</em></strong>
<em><strong>Baggins</strong></em>
<em><strong>Bagg</q 5 D y Ustrong><strong>ins</strong></em>
<em><strong>Bagg</strong></em><strong><em: S t Z n ` >ins</em></strong>
假设我们要做好修正器,那么Y v q K g 8第一个得处理的,就是要处理 DOM 内容和可视化内容之间的映射联络,这种映射联络它能容许我们在序列化和反序列化之后都得到一个规范答案。因此,ContentEditable 的基础上,HTML 的可嵌套灵敏度又将会L $ k z (成为修正器的一个障碍 — 看到的和得到的不一起。
其二,我们在处理选区的时分,选区的鸿沟8 { 4 x Y也是个问题。Window 的 selD e U ? * l Xection 选区,会有 AnchoH ~ ~ + {r 和 Focus 两个特色,分别代表的从r X T t p 7 v ~ P哪里初步,聚集在哪里。而这两个特色,又会有 offset 的值。当 Anchor 和 Focus 都在一个 node 节点的时分,而且 offset 是一起的l : & c g D A,我们可以以为它: ! (就是一个“光标”。反过来讲,光| T O _标就是一个特其他选区。
在 ContentEditable 的基础上,我们会在选区上遇到一个问题就是光标在哪儿的问题。假设 “|” 代表着光标,那么f 7 d k它可能会不才面的几f ; i J V +个方位中:
|<p><span><^ * ( B o Q Lstrong>hello...
<p>|<span><strong>hella 4 $ k P + ` v jo..M R C.
<p><span>|<strong>hello..) ) H x i.
<p><span><strong>|hello...
在展现效果上,它都表现为在 hello
这个文字的左边,但实践上它可能是在不同层级的 DOM 的节点处。我们要处理的是,把选区尽可能地核算一起,把这些可能性给磨平,内存模型驱动选区的方位,而主 + d ^ ( h 5 E动选区又能解说成为内存模型的描绘。
四个a B o # C U ] H 4要害点
假设我们不想用 Canvas 去完全接收烘托及作业的动作,还想运用阅读器自己就具有的特性,我们可以怎样来处理修正器的这些坑呢?单聚处理 ContentEditable 的问题来说,有四个要害的技p D F 1 6 r能点:
-
一个文档模型,而且可以确8 x ) h N } Z d M保 2 个模型在视觉上持平 -
一种映射联络,DOM 和文档模型之间的映射 -
依据文档模型的出色修正指令 -
能将 DOM 的作业转化为修正操作
这儿用上修正器的内存y 1 j N模型的话,可以非常好了解。我们定义好一种修正时分的内存模型,它可以与 HTML 有相关的映射联络。这种映射联络在于,它可能是通过 Shadow DOM 来与实在 DOM 进行转化。
一起,咱c ( R + 8 ! : X w们可以通过监听实在 DOM 来发起作业,这些作业最后会变成操作我们内存模型的一些指令,例如 insert_text 这样的句子,操作完指令之后,内存模型产生改动,触发虚拟 DOM 的改动然后实在 DOM 产生改动。
Web 修正器的三个层级
对应修正器的不同阶段,我们还可以将他们划分为下面三个层级:5 & v v y }

关于终极方案来说,就是文字处理器时代。+ Q I r O G前有 Office Word,现有 Google Doc,还不断有优质的产品持续呈现。而他们背面,我们看到的,现已不是一个富文本修正器了,而是一个文本处理器。

关于文本处理器来说$ w U w – 8 Q z y,它有着显着的特征:
-
完全抛弃 ContentEditable 的特性 -
从光标的制造,到选区的核算,再到内容的排版,均是经e b L 1 ) |过 Javascript 来结束H y ( ) : + = M,而非阅读器就具有的才华
三、回归钉钉文档O P Q T W @ Z
上述两f h | T W i {个章节为修正器做了简略的前史回想,那么回到我们自己的产品来看,这儿受先要和我们同享的是 — 修正器的j ~ Q p冰山效应。
修正器的冰山

怎样来看待它是w Y ; 1一个修正器,仍是一个技能体系,咱~ N [ D l T 4 % o们应该透过表象看实质。有许多人都来找我要修正器` q ] = E e组件,想拿来即用,但我一般都会去咨询一个问题:你们的场景是什么?许多人的回复都很简略,例x Z , b C * o S如“我需求刺进图文混排”、“我需求@ 人”、“我需求可以刺进链接”…而事实上,这些都只是修正器技能的一小部分功用罢了。
我们可以这样来总结下,用户可以看到的可能是一些格式的改动D d Y w / P,例如加粗、斜体、字体、字号等,还可能是一些具有必定交c t O ^ P {互才华的组件,例如附件的上传下载、图片的放大缩小乃至裁剪、提及的下拉自动选择和音讯发送、链接地址的高亮和跳转…
而这些效果,假@ D 4 .如作为一% F l { R +个修正器组件来说,都是非常冰冷的,你直接可以看到的一个纯组件效果。但实践上,处于冰山之下的,是我们要去处理的一些用户不需求感知的问题,这些是我们修正器技能的难点地点。
-
例如我们会有一些很重很重的组件:你的修正器里在嵌入一个表格,用户需求兼并单元格,批量格式化乃至添加公式,D ^ * = O y T . O它是其他一个垂直领域的才华。但它不能非常直白地参与,而是需求从数据模型上去融合,从用户行为上去统一。 -
例如我们会有一些8 = T o很难描绘的意图辨认问题:仿制粘贴是一个非常频频的操作7 w ,,我们是无法感知到D } 7 & | { i用户从哪里仿制的,但却要做到粘贴完的效果符合用户期望。这儿难点在于我们需求去做许多的规范化,无论用户从 Word、Email、网Q G N e页…仿制,都能粘贴进来,而且可以格式化。 -
例如我们会有一些很长链路的业务需求:要确保用户在你的修正器输入的内容不丢掉,那么是你的修正器供给暂存才华呢?仍是暴露出接口来结束,这些都是处理修正才华时分要处理的。 -
例如我们会通过修正器来结束很厉害的功用:要想结束多人实时修正才华,在不同的客户端运转一个修正器,那现已不是一个修正器所能处理的了,再往基层看,会有网络层的技能 — 长链接、断网重连、心跳机制等。再再深化,会有服务端的调度才华和 OT 操作与分发。
因此,这儿想讲的强调的p ) v D ?,修正器不是一个组件就可以了,而是需求背面的一套体系化的技能来支撑。(这一套技能,我们是可以深化,再包成服务)
宿世此生
回想从做语雀到现在的钉钉文档,在修正器技能这块领域,我想通过下面一张图T g % 来标明不K F _同的阶段:

第一个阶段:M B 3 ; h o文本修正时代
初步的时分,我是用 CodeMirror 去包装了一个 MarkDz : r | –own 修正器的,虽然写源码,但特别受工程师喜爱。但它的技能结束凌乱度就在那,和我们去运用一个 Textarea、Input 其实差异不是很大。
更多仍是说它能按行处理,字符级元素这些特性。当然往深里去做,也会有难点,例如 WebIDE 也是个方向。5 b W q
第二个阶段:协同修正时代
在钉% I ~ 7 ~ ; l 2 I钉文档初期,我们就决定了做多人协同修正的才华b x h M s。它是在富文本修正器的基础上去结束的,但怎样结束多人实时修正才华,在结束的凌乱度和功用上对比,它是非常陡峭的。
通过说起来简略,但结束成本会很高。修正器负责产出修正动作$ b b指令,然后通过网络层与服务器进M l 1行 OT 转化然后分发,修正器客户端的接收到音讯之后持续 Merge 再呈现到用户面前。
这儿导致我们在整个存储架构上产生了改动,存储到服务器的不是一篇文档的全量内容,而是一个个的指令,因此我们会依据这个基础上去结束前史版其他回滚、依据选区的划词议论等技能功用点。
第三个阶段:排版核算时代
现在我们现已进入了凌乱业务期,即将面对的更多的客户诉求是来自于 Office 的修正才华,j * d / 8因此我们的一个方向将是排版核算h S u @ 6 ^才华。
排版的中心有两个,一个在于实时的分页核算才华,一个在于图文混排才华。跟着排版才华的深化,几乎整个文档流会在运转被进行内容的拆分,前端非常了解的重排重绘在这儿被演义到酣畅淋漓。
而我们还需求去做的作业I ! B * ;是,多了一个重排X Z y g K R [ E #核算的功用开J ) 7 ] h J W l销前提下,还得保障每一个字符b y 7的输O T 6 7 q e ^ )入功用。尽可能地保持一个字符在 60ms 之内就能呈现出来。
四、一些幽默的技能点
这一章节,我会同享我们在做修正器时分,发现的一些幽默的技能要害。修正器领域虽然枯燥,但实践上兴趣地点,你会发现这不再是一个很垂直的领域,反而是一个要跨越多个维度的技能复合型领域^ b 2 } G 5 ( !。做一行,爱一行,大约就是说在其间找到I ? | #一个与你 match 的点。
1、键盘按下的空格与其兄弟姐妹们

当你按下键盘空v # t t z h H a :格键的时分,实践上是输入了最常见的一V N + 7 B n个空格 ,它的 Unicode 编码为& f H U+0020
,在 HTML 中它的字符为
,也是 ASCII 32 。 它在文本中代表着一个空白标识,在英语中还起到切开词汇的效果。
键盘按下时分产生的空格并不是唯一的。事实上,它有许多兄弟姐妹,有的前端工程师会非常了解,有的也深藏功与名。让我们来了解一下:
TAB
这个r * ~ D s _ E o应该很了解吧,它其实也是空格的一种。我们可以通过 TAB 按键来结束。
NBSP
这一个词第一眼| k o 2 l u D :会眼熟,给他补偿一下,就变成这样的一个 HTML 字符
,当你再次记起的时分,我会告知你。它上面键盘按下的空格,并不是同一个,而是两个不同的符号。
NBSP 的全称应该是 non-breaking space,标明不连续的空间。它的 Unicode 编码为 U+00A0
,它与键盘按下的空格可以说几乎相同,所占有的空白p z q x j宽度一起。但b P i = y j m它不是一个点那么简略,它会使把一行给打断。
ZWSP
这又是什么?简略来说,9 ) D 0 _ U C L你在运用语雀修正器的时分,现已在不知不w g 3 * Q x i觉O w @ ` = &中运用了许多这种空格了。它的全称为 zeror # P g-width space,标明没有宽度,纯属占坑。他就在j ] ~ w 6你的两个文字中心,很小很小的一个不存在的方位,用来给予光标定位。可以说光标一闪一闪的方位就是它了。
它的 Unicode 编码为 US w 4 4 ) ++200B
,HTML 字符为
。它还有几个比较类似的兄弟, U+200C
和 U+200D
它们之间又有点差异,一个容许刺进字符,反之则不容许。
由此,我们可以得到两个信息:
-
空格是有宽度的
-
空格是有特色的,有的可以折行,z p b 9 b N .有的不容许折行
回头来看,空格的我们庭共有 32 个成员,以上四个如下表:
Unicode | 称号 | 宽度 | 是否不_ 5 k连续 | 描绘 |
---|---|---|---|---|
U+0009 | character tabulation | ] [ | ✅ | Tab |
U6 S J M n J 0 _ I+0020 | space | ] [ | ✅ | 空格 |
U+00A0 | no-break space | ] [ | ❌ | 空格 |
U+200B | zero wi[ J K K J q } a zdth space | ][ | ✅ | 占位空格 |
2、光标其实是一s / % B v { 8个特其他选区

Selection
目标标明用户选择的文本规划或刺进符号的当前方位。它代表Z u @页面中的文本选区,可能横跨多个元素。文本选区由用户拖拽鼠标通过文字而产生。– h + X =
对m U t应 Selection 来说,我们应该了解下它的几个要害的技能术语:
-
锚点 (anchor)
锚指的是一个选区的起始点(不同于HTML中的锚点链接,译者注)。当我们运用鼠标框选= * _ Q一个区域的时分,锚点就是我们鼠标按下瞬间的那个点| N e。在用# ~ = B户拖动鼠标时,锚点是不会变的。 -
焦点 (focus)
选区的焦点是该选区的终点,当您用鼠标框选一个选区的时分,焦点是你的鼠标松开瞬间所记录的那个点。跟着用户拖动鼠标,焦点的方位会跟着改动。 -
规划 (range)
规划指的是文档中连续的一部分。一个规划包含整个节点,也可以包含节点的一部分,例如文本节点的一部分。用户一般下只能选择一个规划,可是有的时分用户也有可能选择多个规划。“. ^ % , 3规划”会被作为Range
目标回来。Range目标也能通过DOM创立、添加、删减。
注:这部分内容摘自 de % S Aveloper.mo2 j u K e /zilla.org/zh-CN/docs/…
”
在做修正器的时分,你会发现一个非常幽默的当地。我们非常了解的“光标”,blingbling 闪耀的居然是一个特其他选区。这个选区的 anchorNode 和 focusNode 是同一个,且他们的 offset 均为 0。
3、回车换行与回J L L r车换段

在富文本修正器中,你的光标在一个阶段里面,按下回车,在不同的修正器有不同的结束,大约是这两种差异:
-
新增一行,内容还在这一个阶段里面。y q ] h X一般是一个
brN ^ o ` ~ f r F
占位符。 -
新增一段,/ B 4内容新起。一般是一个
p
标签。D } G C 2 i ` _
在 **Markdown **的语法中,我们根究到回车的时分,一行和9 q L K o l r一段的结束办法:
-
在一段里面需求换行,一个回车就可以抵达,俗称x i r a i 7 N ^的 SoftBreM L – R h G J hak 。
-
一段文字与下一段文字中心N : | a,会有至少 2 个回i Q X T 4 q v –车符。
要在富文本结束回车换行、回车换段,需求两个不同的动作才华处理:
-
Enter
– 换一段 -
Sw ) 2 1 w w { Ph^ w g r V ^ =ift
+/ Q q = f AEnter
– 换一行
4、奇特的 Emoji 宗族和删去键

在结束修正器的删去动作时,比较惯例的做法是回删一个字符。怎样回删呢?就是取了一个字符的长度来做。但是,当你回删到n B . `一个 Emoji 的时分,幽默的作业便产生了 — 你的 Delete 动作之后,看到的是几个乱码字符。假设你还不了解什么是 UAX #29
的话,那么会在写修正器的时分发现这是多么奇特的一个作业。字符的长度大于 1 了,这样就导致你C B [ n P 5 t T [的删去动作并没有符合预期(坑不是一般大了). – N H l @ k ?
举例:
"".length == 2
详见 UAX#29 www.unicode.org/reports/tr2…
It is important to recognizD H I g Y %e that what the user thinks of$ E | * ) Z Q z + as a “character”—a basic unit of a writing system for a language—may not be just a single Unicode code point. InsZ o C 0tead, that basic unit may be made upA A b Y W g of multiple Unicode code points. To? 3 m , K 6 a E avoid ambiguity with the computer use of the term character, this is called ah d j H @ = B = M user-pere } = n o h 8ceived character. For example, “GD A k” + acute-accent is a user-perceived character: users think of it as a single character, yet is actually represented by two Unicode code points. These user-perceived characters are approximated by what isN U 0 w called a grapheme cluster, which can be determined programmatically.
”
可是,当你深化去了解完这些核算机基础知识8 ] ] –之后,也就恍然大悟了 — 原来写修正器,还可以这么幽默,学到了不少东西。
五、结语
在本次的同享中,暂时也就摘取了我们在修正器进程中的 4 个幽默的事例,其实我们还有许多许多,也有许多是未曾开掘出来的。
许多人和我说,修正器太难了,自己可能无法胜任。其实,我只想说:修正器& k f ; ^ ? ?虽然是一个天坑,但并没有现象的那么难。我们刚好有这样的一个气氛 — 乐意去开掘和应战这一个难以规范化的领域,会去追溯其原理实质,探究其间的微妙* y l . B ! d 3,乐于其间。
欢迎自荐或许引荐,参与咱S 4 R y h 们(钉钉文档团队),也欢迎与我沟通。我是展新,我在钉钉,所以我的联络办法,加我请补白(钉钉文档技能同享):
钉钉手刺(首选) | 微信手刺 |
---|---|
![]() |
![]() |
本文运用d O e 3 S 1 m O S mdnice 排版