最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

重學(xué)編輯器

2023-05-17 13:51 作者:支付寶體驗科技  | 我要投稿

?????♀? 編者按:本文作者是螞蟻集團前端工程師天授,讀完本文可以了解:編輯器的技術(shù)發(fā)展歷史、重點模塊的技術(shù)方案。

一、引言

編輯器是人機交互最復(fù)雜的場景之一,核心需求是 WYSIWYG,所見即所得。

核心痛點也一直很明確:WYSIWYG 的衍生需求越來越復(fù)雜、實現(xiàn)難度越來越大:

WYSIWYG

生產(chǎn)實現(xiàn)

文本輸入、查找替換、撤銷/恢復(fù)、高亮

看似簡單,但實際操作表現(xiàn)有很多不確定性

表格、內(nèi)容嵌套、標(biāo)注等操作

圖表、畫板、代碼塊等內(nèi)容形式

音、視頻等數(shù)據(jù)格式

瀏覽器廠商對標(biāo)準(zhǔn)的實現(xiàn)各不相同,帶來兼容性問題

多人協(xié)同、云編輯服務(wù)

對編輯器及其背后的服務(wù)、存儲架構(gòu)設(shè)計都是考驗

跨/多端使用、復(fù)雜排版

性能瓶頸

...

...


二、編輯器技術(shù)的發(fā)展

從我們可以對著計算機屏幕按鍵編輯開始,WYSIWYG(所見即所得)就成為了最基本的需求,編輯器也由此誕生。

終端編輯器

在沒有 GUI 的時代,各系統(tǒng)的終端就是最早的編輯器。早期需要通過各種終端設(shè)備(如 TTY https://en.wikipedia.org/wiki/TTY)和計算機交互;后來操作系統(tǒng)內(nèi)核直接提供了虛擬控制臺作為終端模擬器,接收鍵盤輸入、調(diào)用圖形接口、把結(jié)果渲染到顯示器。

GUI 出現(xiàn)后,操作系統(tǒng)上有了終端窗口(也屬于終端模擬器),直到現(xiàn)在一直被我們普遍使用。

傳統(tǒng)文本/富文本編輯器

GUI 時代文本編輯器窗口軟件很快出現(xiàn)了,圍繞 WYSIWYG,功能還是以下四方面:文本修改、查找替換、撤銷/恢復(fù)、內(nèi)容高亮。同時瀏覽器上也可以編輯,比如 Input 和 Textarea 實現(xiàn)的文本編輯器。

隨著用戶對圖片、圖表排版等需求興起,富文本編輯器出現(xiàn)了,比如 Word。最早的 Word for DOS (https://winworldpc.com/product/microsoft-word/1x-dos)顯示的是內(nèi)容標(biāo)識符、還不算 WYSIWYG,但 1985 年 Word for Mac (https://winworldpc.com/product/microsoft-word/1x-mac)已經(jīng)是 WYSIWYG 的富文本編輯器了。

基于瀏覽器自帶編輯能力的 Web 編輯器

隨著主流(幾乎是全部)瀏覽器支持了 contentEditable(https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable),有了編輯富文本的能力,Web 編輯器開始興起。相比傳統(tǒng)的文本圖片編輯,Web 編輯器需要考慮互聯(lián)網(wǎng)格式數(shù)據(jù)和 HTML 結(jié)構(gòu)的解析。

這也是開源社區(qū)最關(guān)注的方向,以下是幾個有代表性的項目:

CKEditor (2008 至今)(https://github.com/ckeditor/)

CKEditor 1-4(CKEditor 3 之前 FCKEditor)的方案是早期經(jīng)典:

  • 通過打開 contentEditable 讓頁面內(nèi)容可編輯

  • 通過原生 execCommand 即瀏覽器默認(rèn)行為實現(xiàn)大部分內(nèi)容編輯

  • 通過自定義拓展 execCommand 自主更新 DOM,實現(xiàn)非瀏覽器默認(rèn)的編輯操作

  • 輸出是 HTML 字符串直出,因此通過約束嵌套 HTML DTD(https://www.w3.org/TR/html4/sgml/dtd.html) 和內(nèi)容過濾規(guī)則、保證內(nèi)容的正確性。

CKEditor 5 (https://github.com/ckeditor)相比之前就改頭換面了,對 DOM Tree 的數(shù)據(jù)操作做了抽象,并支持了協(xié)同編輯,他在 2020 年發(fā)布,其實借鑒了不少后備編輯器的思路。

KissyEditor(2010)(https://github.com/hotoo/kissy-editor)

淘寶的經(jīng)典產(chǎn)品:

  • 在使用方法上基于 Kissy 的模塊化機制和組件基礎(chǔ)。

  • 編輯器核心其實也借鑒了一部分 CKEditor 的經(jīng)典方案,封裝了 execCommand 的命令系統(tǒng),定制了 HTML DTD 和數(shù)據(jù)過濾規(guī)則。

  • 對 Select 和 Range 的兼容問題有自主實現(xiàn)。

Quill (2012 至今)(https://github.com/quilljs/quill)

Quill 最大的特點是在 View 視圖之外,抽象了 Model 模型層的概念:

  • 內(nèi)容編輯能力上依然靠打開 contentEditable 實現(xiàn)

  • 對 DOM 和數(shù)據(jù)的修改做了一層抽象。

    • 通過 Delta (類似 JSON) 描述數(shù)據(jù)操作變化

    • Parchment 和 Blots 描述要渲染的 DOM

  • 瀏覽器默認(rèn)支持的編輯,MutationObserver 監(jiān)聽后觸發(fā) Delta 變化。

  • 非瀏覽器默認(rèn)或復(fù)雜的操作,直接更新 Delta。

Quill 的新型設(shè)計很受歡迎,由此編輯器們都開始吸收主流框架的架構(gòu)理念,也開始陸續(xù)有了編輯器框架。

ProseMirror (2015 至今)(https://github.com/ProseMirror/prosemirror)

ProseMirror 在 Quill 的基礎(chǔ)上更進一步,是和 React 很像的思想:

  • 純 JSON 描述數(shù)據(jù)內(nèi)容。

  • 重新定義了數(shù)據(jù)到 DOM 渲染的 Schema,為了支持任意格式輸出。

  • 引入了 Virtual DOM,代理了瀏覽器默認(rèn)行為,把編輯操作轉(zhuǎn)成了數(shù)據(jù)操作。

  • 引入了不可變數(shù)據(jù),進而實現(xiàn)了編輯操作的單項數(shù)據(jù)流。

注意 ProseMirror 借助了 contentEditable,但并沒有直接讓瀏覽器處理用戶輸入。

Draftjs (2015-2020)(https://github.com/facebookarchive/draft-js)

Draftjs 來自 Facebook,是一個跟 React 深度結(jié)合的開源項目,受眾很廣。直接將 React 作為視圖渲染層。

  • 打開了 contentEditable,但沒有直接讓瀏覽器處理用戶輸入。

  • 定義了數(shù)據(jù)到渲染規(guī)則的 Schema,每一行都是一個 Block。

  • 結(jié)合了 React 的 Virtual DOM、不可變數(shù)據(jù)、狀態(tài)管理。代理了光標(biāo)、鍵盤操作。

  • 插件系統(tǒng)

React 讓 Draftjs 的上手難度降低、架構(gòu)穩(wěn)定性更好,但這套Schema 在當(dāng)時帶來了很多問題。我們業(yè)務(wù)中的項目在使用 Draftjs (2019 年左右)的時候也遇到不少坑:

  • 每一行都是一個 Block,Inline 元素也被定義成 Block,無法嵌套。

  • 插入的外部 HTML 如果想解析就得手動 parse 適配這套 Schema,自定義很多 Block Entity。

因為種種原因,F(xiàn)acebook 在 2020 年斷更了它另開新坑。

Slatejs (2016 至今)(https://github.com/ianstormtaylor/slate)

Slatejs 的架構(gòu)和分包設(shè)計很有特色,他自己的定位是編輯器框架,語雀前期也是基于 Slatejs 開發(fā)。Slatejs 一開始吸取了前者們的各種優(yōu)點,Schema 和數(shù)據(jù) Model、不可變數(shù)據(jù)。它的核心是實現(xiàn)了對編輯狀態(tài)的 change ,以及 change 的鏈?zhǔn)秸{(diào)用。

之后一直在迭代優(yōu)化:

  • 把一系列 Command 封裝成 Transform,提供了插件機制。

  • 獨立視圖層,并融入自己的插件體系,以插件形式提供對各類 UI 框架的支持。

  • 用簡潔的 immer 實現(xiàn)不可變數(shù)據(jù)。

  • 持續(xù)優(yōu)化 Schema ,甚至在新版移除了 Schema,改成通過 normalizeNode 校驗,Slatejs 提供了內(nèi)置約束、支持自定義約束。

  • 支持用 JSX 創(chuàng)建 Slatejs 的數(shù)據(jù)

BlockSuite (2022 至今)(https://github.com/toeverything/blocksuite)

隨著 Notion 的橫空出世,Block 風(fēng)格的編輯器成為了新潮流。Block 的思想在之前的編輯器中也出現(xiàn)過(比如 Draftjs),但 Notion 在市場上的成功才真正教育了用戶去使用 Block、無所謂 Inline,包括很多老牌筆記也都做了 Block 風(fēng)格的改版支持,比如思源筆記。

從 Notion API 文檔 中可以看出他的設(shè)計思想。中文社區(qū)里也有嘗試分析過 Notion 的實現(xiàn),探索 Notion 的實現(xiàn)(https://zhuanlan.zhihu.com/p/152964640)、Notion 編輯器是怎么實現(xiàn)的(https://www.yuexun.me/blog/how-the-notion-editor-is-implemented):

  • Block 限制了編輯的可操作范圍,其實是將編輯操作的處理簡化了。

  • 重點其實在于 Block 如何組織頁面,Notion 依然基于 DOM(Flex)去排版。

  • Block 內(nèi)部的編輯依然依賴于 contentEditable,同樣有 Schema,有操作事件的監(jiān)聽和渲染。

  • 由于 Block 的存在,對光標(biāo)和 select 選區(qū)高亮做了獨立處理,支持跨 Block 選區(qū)。

不過在開源社區(qū),看著讓人眼前一亮的項目不多,BlockSuite 是少有的優(yōu)秀開源框架:

  • BlockSuite 使用了 Yjs 去支持協(xié)同編輯,并基于 Yjs 的 Shared Types 拓展了一層 Typed Block Tree,實際是借助了 Yjs 去組織 Block。

  • 在視圖模型層面,沒有自定義 Schema、Model,也是借助了 Yjs 實現(xiàn)操作狀態(tài)管理。

  • 在 Block 內(nèi)部編輯操作被簡化了(前面提過),Block 實例之間則互不影響。

  • 渲染層面,不局限于前端框架,甚至不局限于 DOM 實現(xiàn)了 Web Components 的視圖渲染,還提供了 基于 Canvas 的渲染器(https://github.com/toeverything/blocksuite/tree/master/packages/phasor) 用于白板繪圖。

  • 支持本地數(shù)據(jù)持久化。

BlockSuite 的設(shè)計理念跟前輩編輯器框架們完全不同,也是新一代只是協(xié)同工具 AFFiNE (https://affine.pro/)的底層依賴。

脫離瀏覽器自帶編輯能力的 Web 編輯器

即便前面我們提到 Block,即便可能不再直接開放 contentEditable ,但最終還是會依賴 DOM 、依賴 contentEditable。

有些廠商可能會追求交互表現(xiàn)的跨平臺系統(tǒng)的一致性、高性能、高拓展性,不想被 contentEditable 的不可靠表現(xiàn)卡脖子。那么另一條路就是完全脫離瀏覽器的編輯能力,完全自主實現(xiàn)編輯能力。目前看到的幾條思路:

  • 不依賴 contentEditable,但依然依賴 DOM 的排版,自主實現(xiàn)選區(qū)和光標(biāo)、攔截輸入操作和事件,比如有道云筆記(https://www.cnblogs.com/163yun/p/9210457.html)。

  • 不依賴 contentEditable,依賴 DOM 顯示內(nèi)容,但基于瀏覽器或系統(tǒng) API 又自主實現(xiàn)了一層文本布局引擎,包括光標(biāo)、選區(qū)、字體渲染,比如 2010 年 ~ 2021 年的 Google Doc(https://drive.googleblog.com/2010/05/whats-different-about-new-google-docs.html)

  • 完全不依賴 DOM,基于 Canvas 實現(xiàn)排版、光標(biāo)、選區(qū)、輸入和事件,比如 2021 年后的 Google Doc、騰訊文檔。

這難度顯然很夸張,最重要的點是排版怎么做。即便不了解完整的排版怎么實現(xiàn),想想瀏覽器的回流和重繪就可以一斑窺豹。

另外前面說的 Word,雖然本身就不是 Web 編輯器,但在上個世紀(jì)就給出了脫離瀏覽器 DOM 做排版的方案,理論上完全可以移植到 Web 上來。

思路總結(jié)

總結(jié)下前述各種方案的思路,依次如下:

  • HTML

  • contentEditable + document.execCommand + HTML

  • contentEditable + Custom Command + Selection/Range API + HTML

  • contentEditable + DataModel + Schema

  • contentEditable + Block Tree + HTML/Canvas

  • Layout Engine + HTML/Canvas

很多人會把他們定義成不同時代或不同級別(L0/L1/L2)的編輯器技術(shù)。我理解也不一定完全正確,這幾種方式其實并沒有明顯的代差,你也不能說后期的方案一定比早期更好,很多編輯器融合了不同時代的技術(shù),市場上不同技術(shù)的產(chǎn)品也依然存在。

三、重點技術(shù)

下面再梳理下前面提到的一些重點技術(shù)和問題。

架構(gòu)設(shè)計

無論各種編輯器怎么更新?lián)Q代,架構(gòu)上普遍還是經(jīng)典的 MVC 模型。

Model

不同框架產(chǎn)品可能會有更細致的劃分,總體看是考慮兩方面

  • 一是視圖渲染 ViewModel,也是我們最常提到的 Model,直接的表現(xiàn)就是 Schema 的設(shè)計,關(guān)系到數(shù)據(jù)如何映射到視圖的渲染、視圖如何被解析成數(shù)據(jù)。最當(dāng)然也可以沒有 Schema 只做元數(shù)據(jù)校驗,比如新版的 Slatejs。

  • 二是數(shù)據(jù)存儲 DataModel,從而支持文件加載,文件緩存、導(dǎo)出。

這兩種 Model 一般可以互相轉(zhuǎn)化,過程就涉及到了數(shù)據(jù)解析和校驗、序列化,比如像 Obsidian 之類本地化的編輯器,數(shù)據(jù)存儲就是 Markdown,加載時解析成 ViewModel,存儲時需要校驗、序列化。

View

從視圖模型渲染到真正可編輯的視圖 UI,這一層也就是渲染層。前面提到絕大部分基于 HTML 默認(rèn)的可編輯能力,借助 contentEditable,也有自主實現(xiàn)光標(biāo)、選區(qū)和排版的,甚至有脫離 DOM 用 canvas 實現(xiàn) View 層的。

Controller

其實是決定這框架/產(chǎn)品的易用性。這一層通常會攔截各種事件,修改視圖模型,觸發(fā) View 層的變化。涉及三方面:

  • 事件,如果不脫離 DOM,通常會 MutationObserver 監(jiān)聽瀏覽器事件,如果基于 Canvas,就需要自己記錄鼠標(biāo)事件,自己做像素檢測定位等。

  • 修改視圖模型,可以基于 execCommand 做自定義拓展處理,也可以完全自定義一套命令,這個視圖 Model 的設(shè)計也有關(guān)系,修改的記錄也讓文檔有了狀態(tài)。

  • 觸發(fā) View 層變化,如何管理狀態(tài),很多框架會做命令封裝(比如 Quill 的 Transform),設(shè)計事件派發(fā)機制(比如 ProseMirror 的 transaction),借助前端框架(比如 React)維護單向數(shù)據(jù)流并渲染。

contentEditable 和渲染

一致性問題

contentEditable 讓瀏覽器有了編輯富內(nèi)容的能力,但 contentEditable 帶來的重要問題是一致性。這篇經(jīng)典的 Why ContentEditable is Terrible (https://medium.engineering/why-contenteditable-is-terrible-122d8a40e480)就談到了這個問題:

  • 由于 HTML 標(biāo)簽本就可以嵌套組合,描述同一個樣式就有多種可能:

<strong><em>Baggins</em></strong>
<em><strong>Baggins</strong></em>
<em><strong>Bagg</strong><strong>ins</strong></em>
<em><strong>Bagg</strong></em><strong><em>ins</em></strong>

甚至給 span 標(biāo)簽加上 CSS 樣式也可以實現(xiàn)。瀏覽器里的實現(xiàn)不盡相同,因此不同瀏覽器里互相編輯的產(chǎn)物就不可控了。

  • 在處理選區(qū)時同樣也會有問題,一個光標(biāo)( 用 『|』 表示)看起來在某個文字前面,但具體在那個 HTML 標(biāo)簽處,完全不可知:

|<strong><em>Baggins</em></strong>
<strong>|<em>Baggins</em></strong>
<strong><em>|Baggins</em></strong>

另外比如字體渲染,絕大多數(shù)情況下,瀏覽器都 fallback 到了系統(tǒng)渲染,比如 Mac OS X 使用 CoreText渲染引擎,Windows 一般是 DirectWrite 等,渲染上也會有不一致性。

這些理論上都需要編輯器實現(xiàn)時候處理。

渲染上的選擇

另一方面,引用 BlockSuite 作者 doodlewind (https://github.com/doodlewind)的表述(https://www.zhihu.com/question/366666295/answer/976815981)『在瀏覽器里,打開了 contentEditable 不等于借助了 contentEditable』

  • 借助了 contentEditable,意味著依賴了瀏覽器默認(rèn)的富文本編輯行為,早期編輯器基本都是這套方案,

  • 頁面打開了 contentEditable,意味著頁面這個區(qū)域內(nèi)具備了進行富文本編輯的能力,但具體如何實現(xiàn)編輯行為,其實是框架或編輯器自己控制的。

早期 CKEditor 是前者,渲染層交給 HTML 自己;Draftjs、Slate.js 就是后者,將 View 渲染層交給了他們依賴或運行時的前端框架(比如 React)。

渲染層交給 HTML,好處就是 contentEditable 只是更改內(nèi)容,實際渲染性能幾乎不會有問題。渲染自行調(diào)度或者交給前端框架,比如 React,重渲染、狀態(tài)刷新有時候反而會帶來性能問題,比如不可變數(shù)據(jù),本身還是有大量計算,尤其在內(nèi)容多的時候。

前面提過,Slatejs 就在持續(xù)簡化自己的 Schema 結(jié)構(gòu),直到最后移除了 Schema,改成通過 normalizeNode 校驗。但這其實也就把難度交給了開發(fā)者,如果要粘貼外源的富文本,就需要開發(fā)者寫各種兼容邏輯。

所以數(shù)據(jù)驅(qū)動、框架調(diào)度等方式看似高級,但并非完美的方案,實際還是要看場景。比如語雀后來還是放棄了 Slatejs,改自研,自研的實現(xiàn)是前面提到的方式:contentEditable + Custom Command + Selection/Range API + HTML 在渲染層擺脫了 React。

輸入事件

一個 DOM 元素打開了 contentEditable 屬性,execCommand 也會作用于光標(biāo)區(qū),但 execCommand 觸發(fā)的一些瀏覽器默認(rèn)行為會導(dǎo)致交互不可預(yù)期。:

  • 選區(qū)拖選、光標(biāo)、按鍵時的默認(rèn)行為,前面提過,有不一致性

  • 輸入文字時的默認(rèn)行為,但涉及到輸入法時會不可控

  • 復(fù)制粘貼時的默認(rèn)行為,涉及到 HTML 富文本格式時會不可控

如果希望編輯器的行為可控,我們可以想到自主定義 Command 指令集(像早期的 CKEditor)。目標(biāo)都是讓整個操作可控,對輸入、復(fù)制粘貼等做攔截,阻止瀏覽器默認(rèn)行為跳過視圖模型直接更新 DOM 的行為。

  • 解法一:使用 contentEditable 的 beforeinput (https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event)(比如 Draftjs、Slatejs 都用過,任何處于 designMode 的元素都可以用)。但存在瀏覽器兼容問題,以及對輸入法編輯器的兼容問題:

    • 中文輸入的時候:一方面用輸入法全選輸入,輸入法編輯器自己會直接改 DOM;另一方面onBeforeInput 會觸發(fā)視圖模型去更新 DOM,但此時 DOM 結(jié)構(gòu)可能已經(jīng)被輸入法改變。導(dǎo)致視圖模型和實際 DOM 對不上。

  • 解法二:在解法一基礎(chǔ)上借助?compositionstart(https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event)/compositionend?(https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event)事件,compositionstart 會在輸入法編輯器時觸發(fā),選詞結(jié)束時觸發(fā) compositionend;因此可以在觸發(fā) compositionstart 時,備份選區(qū)的 DOM,當(dāng) compositionend 時恢復(fù)選區(qū)的 DOM。

  • 解法三:加上 MutationObserver 監(jiān)聽 DOM 變化,一旦出現(xiàn)變動,同步 DOM 的更改到視圖模型。這其實就是做了雙向數(shù)據(jù)流,遇到問題會難排查一些。

選區(qū)和光標(biāo)

選區(qū)和光標(biāo)系統(tǒng)也是編輯器的核心。DOM 也提供了相關(guān) API,前面提到的 Selection/Range API。

  • Selection 對象表示選擇的選區(qū)或插入符號的當(dāng)前位置

  • Range 對象表示被選中的文檔片段

  • ::selection?匹配被選中的部分,修改選區(qū)樣式

通常是操作 Selection 對象對應(yīng)的 Range 對象。實際中還有多 Range 的場景,如果有很多自定義 Block,那就還要處理跨 Block 選區(qū)等問題。

光標(biāo)其實就是是一種特殊的選區(qū),選中區(qū)域被壓縮成一個點。由于 HTML 結(jié)構(gòu)的不確定性,編輯器們經(jīng)常需要自己處理光標(biāo)定位(比如同級子元素如何定位、回車換行時光標(biāo)定位等問題),有的定位方式是絕對定位,有的是相對定位加偏移量。

Canvas

HTML 自己直接渲染相比 React 等框架二次調(diào)度會好很多,但在性能要求高的場景下又顯得不夠好。比如在 Google Doc 看來,HTML渲染過程中的回流、重繪開銷太大,他需要接管整個文字處理區(qū)域的布局、排版,從字體解析到光柵化都要自己控制,基于 Canvas 實現(xiàn)整個編輯器。

由于沒有了天然可編輯區(qū)域,前面提到的幾方面事情都需要基于 Canvas 自主實現(xiàn),我們可以簡單思考下:

選區(qū)和光標(biāo)

通過記錄鼠標(biāo)事件,獲取鼠標(biāo)選區(qū)位置,用絕對定位遮罩實現(xiàn)選區(qū)高亮的效果;如果計算出操作位置相同,則插入光標(biāo)代替遮罩;點擊后的選中狀態(tài),沒了 contentEditable,就需要自己計算坐標(biāo)對應(yīng)那個文字,進而意味著要解析字體參數(shù)做排版。

排版

顯然是天坑,根據(jù)圖可視化上的做法可以猜測下:在 Canvas 中用 measureText 測量文字寬高,文本數(shù)據(jù)可能是發(fā)送到 Web Worker, Worker 中根據(jù)可視寬高、排版規(guī)則,分配每個頁面的字符,計算結(jié)果返回主線程,主線程計算每個字符的具體位置。

輸入事件

首先輸入法編輯器的相關(guān)事件捕獲不到了,如果不使用 input、 contentEditable 這些 designMode (https://developer.mozilla.org/en-US/docs/Web/API/Document/designMode)元素,就沒有辦法輸入中文。Google Doc 的方法是在光標(biāo)后面藏一個隱藏的 input 和 contentEditable 元素,不參與排版展示,僅用來觸發(fā)輸入法編輯器的輸入事件。其他操作,還得結(jié)合點擊定位實現(xiàn)一個事件系統(tǒng)。

圖形拾取

沒有了 DOM 的天然標(biāo)簽區(qū)分,Canvas 中文字、線條、表格、圖片如何區(qū)分識別,就需要持續(xù)優(yōu)化比如包圍盒的計算等。

局部渲染

在一整塊 Canvas 中如果只修改一小段文字,顯然不能全量重渲染,所以需要判斷用戶操作的影響范圍,計算出需要局部渲染的包圍盒,局部刷新。

依然想提一下,Word 在上個世紀(jì)就給出了脫離瀏覽器 DOM 做排版的方案,理論上完全可以移植到 Web 上來。

Block

Block Style 的編輯器把每個元素塊當(dāng)作獨立的 Block,Block 內(nèi)部用 contentEditable 的 DOM 元素來實現(xiàn)接收用戶的編輯輸入。這種做法的好處是,編輯可操作、可能出問題的范圍都變得更加可控,從而降低了整個編輯器實現(xiàn)的復(fù)雜度。

而缺點可能是 inline image 這種看起來應(yīng)該天然支持的需求不好實現(xiàn)。但 Notion 們通過優(yōu)秀的產(chǎn)品體驗,成功教育了用戶別去用 Inline 圖片,大家都接受了 Block Style 模塊化的編輯器。

前面提過 Draftjs 其實很早就引入了 Block 思想,每行都是 Block,各種元素都是 Block、都做成 entity,開發(fā)者可以拓展,但也讓編輯器內(nèi)容的開發(fā)變得復(fù)雜。Notion 也許參考了 Draftjs 的實現(xiàn),但將 Block 簡化了,要么是純富文本的 contentEditable 類型的 Block,要么就是完全不同的其他類型(完全自定義編輯行為或直接不可編輯)。

Block Style 的編輯器,核心已經(jīng)不在是處理內(nèi)容編輯,而是構(gòu)造一個類富文本文檔的視圖讓用戶可交互,重點在于處理 Block 和 Block 之間的關(guān)系。比如 Notion 是有自己定義的 Schema 結(jié)構(gòu) ,布局排版依賴瀏覽器的 Flex 布局。而 BlockSuite 則是借助了 Yjs 去管理 Block 的關(guān)系。

Yjs 本身提供了 YText、YMap 等常用數(shù)據(jù)類型 Shared Types,可以作為 Model 層,拓展 Shared Types 就可以適配各種 Model,因此已經(jīng)有了不少 Yjs 對編輯器的 bindings。感興趣可以看 Yjs 的文檔。

協(xié)同

現(xiàn)代編輯器大多都會實現(xiàn)多人實時編輯能力,復(fù)雜度和成本很高。兩種基礎(chǔ)方案:OT 和 CRDT

  • OT (Operational transformation)操作轉(zhuǎn)化;包括操作和轉(zhuǎn)換兩步,適合用于純文本。OT 操作必須通過服務(wù)器的轉(zhuǎn)換才可以合并。

  • CRDT (Conflict-free replicated data type) 無沖突可復(fù)制數(shù)據(jù)類型;CRDT 由于其數(shù)據(jù)結(jié)構(gòu)特性,不通過服務(wù)器也可以合并,在分布式系統(tǒng)的最終一致性上也有應(yīng)用。

設(shè)計上有 Model 層、有 Schema 的編輯器其實很容易和 OT 結(jié)合,Model 層描述一系列原子化操作,OT 負責(zé)把不同用戶的原子化操作合并。

而前面提到 BlockSuite 使用的 Yjs 也是一個前端 CRDT 基礎(chǔ)庫。

具體算法實現(xiàn)不是本文的重點,重點在于編輯器 Client 是負責(zé)產(chǎn)出動作指令、動作效果合并展現(xiàn),服務(wù)層 Server 即便 CRDT 不做合并也需要廣播分發(fā),所以也涉及到整個后端數(shù)據(jù)存儲架構(gòu)、甚至網(wǎng)絡(luò)層的設(shè)計。

所以協(xié)同編輯領(lǐng)域還有非常多的事情要做。

四、展望

本文大致梳理了編輯器的技術(shù)發(fā)展歷史、重點模塊的技術(shù)方案。大方向上,編輯器技術(shù)的幾種實現(xiàn)思路、核心技術(shù)難點都是比較明確的,上面提到的每種方案都還有很多坑要填,需要持續(xù)探索。

最大變數(shù)可能是支持多模態(tài)的 GPT 帶來的交互革命。如果自然語言能夠成為一種普及的交互模式,那么編輯器甚至是所有 GUI 軟件的價值都會變化,也許所有的編輯器都只需要一個文本對話框。不過這是另一個話題了,也不知道這一天什么時候會到來。

拋開 GPT 潛在的交互革命,立足現(xiàn)在面向未來的編輯器,可能更多會專注于特定領(lǐng)域,針對不同的領(lǐng)域場景做技術(shù)優(yōu)化。拋磚引玉一下:

  • 代碼編輯器,前面本文沒怎么提,像 MonacEditor、CodeMirror 都是社區(qū)里的長青項目,包括 WebIDE 之類的產(chǎn)品,關(guān)注重點一是響應(yīng)速度,優(yōu)化智能提示,優(yōu)化 LSP,關(guān)心的是 Editor 如何更快的和 LS 交互;關(guān)注重點二是生態(tài)建設(shè)、如何提供開箱即用的體驗。

  • 圖形編輯器,像 Figma,技術(shù)上目標(biāo)是突破瀏覽器的瓶頸、嘗試通過 WASM、Skia (比如 CanvasKit)優(yōu)化渲染速度,功能上會更關(guān)心諸如無限畫布、圖形繪制,以及對整個設(shè)計生態(tài)的支持。

  • 通用文檔編輯器,比如語雀通過 Canvas 實現(xiàn)表格編輯器、通過 SVG 實現(xiàn)思維導(dǎo)圖編輯器,關(guān)鍵詞是靈活、合適,把合適的技術(shù)用在合適的場景上,讓整個產(chǎn)品足夠靈活,講究的是場景覆蓋。

  • 報告等專業(yè)編輯器,這類容易跟垂直行業(yè)掛鉤的編輯器,既要深入到具體業(yè)務(wù),又要避免定制、考慮通用的問題,我理解重點還是提高編輯效率、數(shù)據(jù)校驗核準(zhǔn)。

??引用

  1. https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/contenteditable

  2. https://github.com/ckeditor/

  3. https://github.com/hotoo/kissy-editor

  4. https://github.com/quilljs/quill

  5. https://github.com/ProseMirror/prosemirror

  6. https://github.com/facebookarchive/draft-js

  7. https://github.com/ianstormtaylor/slate

  8. https://github.com/toeverything/blocksuite

  9. https://developers.notion.com/reference/block

  10. https://zhuanlan.zhihu.com/p/152964640

  11. https://www.zhihu.com/question/366666295/answer/976815981

  12. https://www.yuexun.me/blog/how-the-notion-editor-is-implemented

  13. https://block-suite.com/introduction.html

  14. https://www.cnblogs.com/163yun/p/9210457.html

  15. https://drive.googleblog.com/2010/05/whats-different-about-new-google-docs.html

  16. https://medium.engineering/why-contenteditable-is-terrible-122d8a40e480

  17. https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/beforeinput_event

  18. https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionstart_event

  19. https://developer.mozilla.org/en-US/docs/Web/API/Element/compositionend_event

  20. https://developer.mozilla.org/en-US/docs/Web/API/Document/designMode

  21. https://docs.yjs.dev/

重學(xué)編輯器的評論 (共 條)

分享到微博請遵守國家法律
冷水江市| 亚东县| 呈贡县| 西和县| 昆明市| 娄底市| 张家川| 紫金县| 鄱阳县| 石棉县| 和静县| 武川县| 永春县| 栖霞市| 深泽县| 太和县| 奉新县| 九寨沟县| 肃北| 济南市| 安岳县| 东方市| 县级市| 樟树市| 双江| 星子县| 凭祥市| 靖远县| 道孚县| 井研县| 镇原县| 呼图壁县| 浪卡子县| 平阴县| 贞丰县| 汉寿县| 宜丰县| 修文县| 武城县| 乌拉特后旗| 临夏县|