js input 自动换行_深入Slate.js - 拯救 ContentEditble
我們是釘釘的文檔協同團隊,我們在做一些很有意義的事情,其中之一就是自研的文字編輯器。為了把自研文字編輯器做好,我們調研了開源社區各種優秀編輯器,Slate.js 是其中之一(實際上,自研文字編輯器前,我們就使用了很久的 Slate)。
我們團隊的同學把對 Slate 的理解,寫成了小冊子,想通過連載的形式分享給你,下面是小冊子的大綱及第 2 篇 - 「拯救 ContentEditble」。
TOC
- 一行代碼實現富文本編輯器
- 拯救 ContentEditable
- Slate.js 設計
- HTML 中的富文本
- Slate.js 中的富文本
- 節點尋址
- 附錄 - 不可變數據
- 附錄 - Memorize
- Slate.js 是怎么工作的
- 大腦 - Controller
- 指令系統
- Operation
- 插件體系
- Normalize
- Decoration
- Annotation
- 模型與視圖的同步
- Tiny Slate.js:實現一個 Mini Slate.js
- 設計數據結構
- 設計 Controller
- 實現編輯器組件
- Slate.js 生態現狀
- 兼容其他格式
- 單元測試
- 移動端編輯器
- 挑戰與變革
- 難于完美的編輯器
- 大躍進 - Slate.js 0.50
- 附錄 - 協同理論
- OT 算法
- 協同調度
- 關于作者
在富文本編輯器出現之前,瀏覽器已經具備了「展示」富文本的能力,開發者可以通過編排 HTML 和 CSS,實現對字號,字色等樣式控制。但對于用戶輸入,瀏覽器所提供的 <textarea /> 和 <input /> 都只允許用戶輸入「純文本」,能力十分單薄。
因此,如果我們能夠直接編輯 HTML 內容,也就具備了「編輯」富文本的能力。例如當我們選中文本 xxx 并按下加粗的快捷鍵后,若能生成 <b>xxx</b> 或者 <strong>xxx</strong> 這樣的 HTML,就能看到被加粗的文本。
瀏覽器為 DOM 節點提供的 contentEditble 屬性即能為節點賦予「編輯其 HTML 內容」的能力。
contentEditable:讓節點可編輯
極早期的富文本編輯器實現中,為了處理換行,就要攔截用戶的鍵盤事件,判斷用戶是按下了回車鍵后,就要為其創建一個新的段落(如生成一個 <p /> 節點),這可能是通過 document.createElement() 或者類似 API 實現的。我們可能認為在新世紀初,這樣的實現方式也持續了很長一段時間,其實早在 2000 年下半年,微軟的 Internet Explorer 5.5 便引入了 contentEditable 特性,顧名思義,讓 IE 瀏覽器中的 DOM 節點的 HTML 可以被編輯。只需要為節點聲明 contenteditable為 true,那么用戶在這個節點按下回車后,IE 就能自動為用戶生成新的段落。
時間再往前推 3 年,IE 4 引入的 designMode 已經能讓整個文檔的 HTML 內容可以被編輯,contentEditable 所做的,是讓開發者能夠更細粒度地控制節點內容的可編輯性。但比較遺憾的是,當時微軟發布的這個特性,除了一個簡要的使用文檔,沒有對可編輯性的內在行為和實現做更多描述。
行為及實現規范的缺乏,導致該特性只能在了 IE 瀏覽器中使用。剛才我們提到的,在 contentEditable 節點下,用戶敲擊回車后,瀏覽器能為之產生一個新的段落,但應該產生什么樣的段落呢?類似的問題卻沒有規范來約束。
WHATWG 成員 Anne van Kesteren 也在 2005 年發表了一篇名為 More on contenteditable 的博文,在其中列舉了同樣內容,但是結構不同的兩個 contentEditable 節點:
<!-- 例子 1 --> <div contenteditable>test</div><!-- 例子 2 --> <div contenteditable><div>test</div> </div>當我們分別在這兩個例子中敲入回車時,在 IE 5.5 中,前者生成的新段落是一個 <p> 元素,而后者生成的卻是一個 <div>。
因此,為了推動 HTML 在瀏覽器中可編輯性的應用,WHATWG 小組開始著力于 contentEditable 的規范制定。同年 7 月,Anne van Kesteren 撰寫了第一版 contentEditable 的規范。最終,經過標準委員會的不斷努力,終于形成了 HTML 5 中的 contentEditable 規范,這也是目前幾乎所有瀏覽器所遵循的規范。
在規范中,定義了兩個角色:
- editing host:即正被編輯的 HTML 元素。如果某個 HTML 元素開啟了 contentEditable 屬性,那么這個元素就是一個 editing host;而如果 document 開啟了 designMode,那么整個 HTML 文檔下的元素都是 editing host。
- editable:即可編輯元素。若 HTML 元素是 editing host 的子孫,那么它就可以被編輯,另外,可編輯元素的子孫也是可編輯的(除非這些子孫被聲明了 contentEditable 為 false)。
document.execCommand :使用命令進行編輯
通過 contenteditable 及 designMode 屬性能讓 HTML 內容能夠被編輯,但是,它們所做的,僅僅是為節點或者文檔開啟 HTML 內容的編輯能力,IE 5.5 引入 contentEditable 特性時,所有的編輯行為都托管給了其自己處理,并沒有對外暴露編輯相關的 API。直到 Firefox 3 問世,其不僅支持了 contentEditable,還配套了能夠與可編輯元素進行互動的 API: document.execCommand
例如,我們想要對選中的文本加粗,就可以執行:
但是,瀏覽器提供的 document.execCommand 并非無所不能,甚至還成為了文本編輯器的實現掣肘,不僅僅是支持的「指令有限」,就連同一個指令,各瀏覽器的「實現都有可能不同」。因此,更多的編輯功能仍然需要開發者進行事件劫持等操作才能實現。
Why ContentEditable is Terrible
自從 contenteditable 被 IE 引入后,用戶在瀏覽器的撰寫文檔時,擁有了更強大的能力,各大瀏覽器廠商也紛紛跟進,但是經過十多年的發展,各個瀏覽器仍然難以戰勝特性背后的復雜性,帶來統一的實現。
幾年前,Medium Editor 的開發者之一 Nick Santos 發表過一篇著名的博文:Why ContentEditable is Terrible?,我們不妨先回顧下這篇博文,一方面了解 contentEditable 的給編輯器造成的困擾,一方面也了解編輯器為此做出了怎樣的應對。
視覺內容與實際內容的一對多關系
令用戶看見的內容為「視覺內容」,視覺內容對應的 DOM 結構為 「實際內容」,在不同的瀏覽器中,雖然用戶看到了同樣的內容,但這些內容背后卻對應了不同的 DOM 結構:
視覺內容與實際內容的一對多關系例如下面這段文本:
The hobbit was a very well-to-do hobbit, and his name was Baggins.在不同的瀏覽器中,有可能形成不同的 DOM 內容:
<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>視覺選區與實際選區的多對多關系
選區的情況則更加糟糕,用戶看到的選區,可能被映射為不同的 DOM 選區;而同一個 DOM 選區,用戶也會看到不同的視覺選區:
視覺選區與實際選區的多對多關系例如,我們的 HTML 如果是:
his name was <strong><em>Baggins</em></strong>用戶看到的光標落在 Baggins 前面,這樣的視覺選區,可以被不同的 DOM 選區表示(我們用 <cursor /> 表示光標):
his name was <cursor/><strong><em>Baggins</em></strong> his name was <strong><cursor/><em>Baggins</em></strong> his name was <strong><em><cursor/>Baggins</em></strong>繼續在光標位置插入字符 I,由于插入位置(DOM 選區)的不同,將形成不同的內容:
- his name was IBaggins
- his name was IBaggins
- his name was IBaggins
假如我們的文本是:
The hobbit was a very well-to-do hobbit, and his name was Baggins.在 well-to- 后面換行,用戶看到的文本內容是:
The hobbit was a very well-todo hobbit, and his name was Baggins.
換行后,DOM 選區則選中了「從第一行末尾到第二行開頭的」,那么怎么將這個選區展示給用戶呢?光標究竟應該落在第一行末尾,還是第二行開頭呢?
DOM 選區可以被映射為不同的視覺選區,也就會造成懸擺選區問題:
主流的編輯器架構
由于 contentEditable 的不可靠,Medium Editor 在架構時,通過下面兩個方式規避上面提到的問題:
- 模型與視圖分離:編輯器自定義視圖無關的數據結構,視圖的渲染不再由瀏覽器控制,而是由編輯器控制,從而滿足「視覺與實際內容的一一映射」,避免在不同的瀏覽器中發散
- 自定義指令:自定義編輯器的指令集,一方面能擴充編輯器能力,但更重要的一方面,是避免直接調用 document.execCommand 在不同瀏覽器形成不一致的結果
目前,主流富文本編輯器的大多也采用了這樣的架構,例如 CKEditor 5、ProseMirror、Draft.js、Slate.js 等。
接下來,我們將深入目前流行的 Slate.js ,更加細致地了解主流富文本編輯器的設計哲學和實現細節。通過閱讀后續章節,你將認識到:
- Web 富文本編輯器的內核模型是怎么設計的,為什么要這樣設計
- 編輯器的模型和視圖之間是如何同步的
- 編輯器是如何通過插件體系擴展能力的
- 編輯器是怎么支持多人協同編輯的
- 當前 Web 富文本編輯器面臨的問題
最后,我們還會一起嘗試造一個簡化版的 Slate.js 來驗證我們的學習成果。
如果你對協同文檔技術感興趣,也可以加入下面的群(釘釘/微信),和我們一同討論。
一起討論技術也歡迎關注本賬號,我們每周都會更新~
參考資料
- The WHATWG Blog
- Why ContentEditable is Terrible?
- Wiki - WYSIWYG
總結
以上是生活随笔為你收集整理的js input 自动换行_深入Slate.js - 拯救 ContentEditble的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 网站被ddos攻击了多久恢复(ddos攻
- 下一篇: linux vi撤销上一个命令(linu
