Skip to content

Commit

Permalink
25/01/12
Browse files Browse the repository at this point in the history
  • Loading branch information
WindRunnerMax committed Jan 12, 2025
1 parent 5622a59 commit 299fbb6
Showing 1 changed file with 65 additions and 6 deletions.
71 changes: 65 additions & 6 deletions Backup/从零设计实现富文本编辑器.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@

我也算是比较关注于各类富文本编辑器的实现,包括在各个站点上的编辑器实现文章我也会看。但是我发现这其中极少有讲富文本编辑器的底层设计,绝大多数都是将的应用层,例如如何使用编辑器引擎实现某某功能等。这些应用层的实现本身也会有一定复杂性,但是底层的设计却是更值得探讨的问题。

而我恰好前段时间都在专注于编辑器的应用层实现,在具体实现的过程中也遇到了很多问题,并且记录了相关文章。然而在应用层实现的过程中,遇到了很多我个人觉得可以优化的地方,特别是在数据结构层面上,希望能够将我的一些想法应用出来。具体来说,主要有下面的几个原因:
而我恰好前段时间都在专注于编辑器的应用层实现,在具体实现的过程中也遇到了很多问题,并且记录了相关文章。然而在应用层实现的过程中,遇到了很多我个人觉得可以优化的地方,特别是在数据结构层面上,希望能够将我的一些想法应用出来。而具体来说,主要有下面的几个原因:

## 编辑器专栏
纸上得来终觉浅,绝知此事要躬行。

我的博客是从`20`年开始写的,记录的内容很多,基本上是想到什么就写什么,毕竟是作为平时学习的记录。然后在`24`年写了比较多的富文本编辑器的文章,主要是整理了平时遇到的问题以及解决方案,集中在应用层的设计上,例如:

- [初探富文本之文档虚拟滚动](https://github.com/WindRunnerMax/EveryDay/blob/master/RichText/初探富文本之文档虚拟滚动.md)
Expand Down Expand Up @@ -48,15 +50,72 @@
<span data-string="true" data-enter="true" data-leaf="true">&ZeroWidthSpace;</span>
```

那么从名字上来看,这个零宽字符在视觉上是不显示的,因为其是零宽度。但是在编辑器中,这个字符却是很重要的。简单来说,我们需要这个字符来放置光标,以及做额外的显示效果。需要注意的是我们在这里指的是`ContentEditable`实现的编辑器,如果是自绘光标的编辑器则不一定需要这部分设计。
那么从名字上来看,这个零宽字符在视觉上是不显示的,因为其是零宽度。但是在编辑器中,这个字符却是很重要的。简单来说,我们需要这个字符来放置光标,以及做额外的显示效果。需要注意的是我们在这里指的是`ContentEditable`实现的编辑器,如果是自绘选区的编辑器则不一定需要这部分设计。

我们先来聊一下额外的显示效果,举个例子,我们在选择飞书文档文本内容,如果选中到文本末尾时,会发现末尾会额外多出形似`xxx|`的效果。在平时不关注的话可能会觉得这是编辑器默认行为,但是实际上这个效果无论是`slate`还是`quill`中都是不存在的。

实际上这个效果就是使用零宽字符来实现的,在行内容的末尾后面插入零宽字符,就可以做到末尾的文本选中效果。实际上这个效果在`word`中更常见,也就是额外渲染的回车符号。

```html
<div contenteditable="true">
<div><span>末尾零宽字符 Line 1</span><span>&#8203;</span></div>
<div><span>末尾零宽字符 Line 2</span><span>&#8203;</span></div>
<div><span>末尾纯文本 Line 1</span></div>
<div><span>末尾纯文本 Line 2</span></div>
</div>
```

那么在这个零宽字符如果只是渲染效果的话,那么可能实际上起的作用并不很必要。但是在交互上这个效果却很有用,例如此时我们有`3`行文本,如果此时从第`1`行末尾选到第`2`行时,并且按下`Tab`键,那么此时这两行的内容就会缩进。

那么如果没有这个显示效果,此时进行缩进操作,用户可能认为仅仅是选中了第`2`行,但是实际上是选中了`1/2`两行文本。这样的话用户可能会以为是`BUG`,而我们也实际接受过这个交互效果的反馈。

```plain
123|
4|x56
```

也对各个在线文档实现进行了简单调研: 基于`contenteditable`实现的编辑器中,飞书文档、早期`EtherPad`存在这个交互实现;自绘选区的编辑器中,钉钉文档存在这个实现;`Canvas`引擎实现的编辑器中,腾讯文档、`Google Doc`存在这个实现。

在渲染效果部分,零宽字符还有一个重要的作用是撑起行内容。当我们的行内容为空时,此时这个行`DOM`结构的内容就是空,这就导致此行的高度塌陷为`0`,且无法放置光标。为了解决这个问题,我们可以选择在行内容中插入零宽字符,这样就可以撑起行内容且可以放置光标。当然使用`<br>`来撑起行高也是可以的,使用这两种方案会各有优劣,且兼容性方面也有所不同。

我们先来聊一下额外的显示效果,
```html
<div data-line-node></div>
<div data-line-node><br></div>
<div data-line-node><span>&#8203;</span></div>
```

选区末尾、撑起行内容
在类似于`Notion`这种块结构的编辑器中,还有个比较重要的交互效果。即块级结构独立选择,例如我们可以直接将整个代码块独立选出来,而不是仅仅能选择其中的文本。这种效果在目前的开源编辑器很少有实现,都是需要自行以块结构重新组织设计选区。

块级结构选择效果
通常来说,这个交互同样可以使用零宽字符来实现。因为我们的选区通常是需要放置在文本节点上的,因此我们很容易可以想到,可以在块结构所在行的末尾放置零宽字符,当选区在零宽字符上时就将整个块选中。这里用零宽字符而不是`<br>的好处是,零宽字符本身就是零宽,不会引起额外的换行。

inline-block
```html
<div>
<pre><code>
xxx
</code></pre>
<span data-zero-block>&#8203;</span>
</div>
```

在结构上,零宽字符还有个非常重要的实现。在编辑器内的`contenteditable=false`节点会存在特殊的表现,在类似于`inline-block`节点中,例如`Mention`节点中,当节点前后没有任何内容时,我们就需要在其前后增加零宽字符,用以放置光标。

在下面的例子中,`line-1`是无法将光标放置在`@xxx`内容后的,虽然我们能够将光标放置之前,但此时光标位置是在`line node`上,是不符合我们预期的文本节点的。那么我们就必须要在其后加入零宽字符,在`line-2/3`中我们就可以看到正确的光标放置效果。这里的`0.1px`也是个为了兼容光标的放置的`magic`,没有这个`hack`的话,非同级节点光标同样无法放置在`inline-block`节点后。

```html
<div contenteditable style="outline: none">
<div data-line-node="1">
<span data-leaf><span contenteditable="false" style="margin: 0 0.1px;">@xxx</span></span>
</div>
<div data-line-node="2">
<span data-leaf>&#8203;</span>
<span data-leaf><span contenteditable="false" style="margin: 0 0.1px;">@xxx</span></span>
<span data-leaf>&#8203;</span>
</div>
<div data-line-node="3">
<span data-leaf>&#8203;<span contenteditable="false">@xxx</span>&#8203;</span>
</div>
</div>
```

### 数据结构设计
数据结构设计
Expand Down

0 comments on commit 299fbb6

Please sign in to comment.