誠如上一篇結尾所說,我們今天要把目光聚焦在瀏覽器提供的 contenteditable
屬性以及 execCommand
api ,這兩個瀏覽器默認,用於製作 WYSIWYG 編輯器的工具。
contenteditable
基本上就是最基本最簡易的 WYSIWYG 編輯器製作方式,它是 HTML5 新增的屬性,讀者現在就可以點開任意一個網站,將幾乎任何的 HTML elements (除了像 <img>
這種無法塞入純文本節點的 tag 以外)加入 attribute : contenteditable="true"
,接著就能編輯文字以及對反白後的文字透過快捷鍵,如: cmd + b
、 cmd + z
等進行加粗或者復原等等的操作。
讓我們拿 MDN 的 contenteditable
介紹頁面 來示範
當我們將 <p>
tag 新增 contenteditable 屬性後我們就能隨意編輯裡頭的內容,如上圖反白一段文字後按下 cmd + b
、 cmd + z
就能加粗或將文字改成斜體,也能新增或修改文字,按 cmd + z
也能 redo ,讀者可以自行玩玩看。
還有另一個與它相似的屬性,也是在 HTML5 時新增的,叫做 designMode
。他們之間的差別就只是前者是用來指定 DOM 區塊,而後者是將整個 HTML 文件切換為可編輯狀態,我們拿 w3schools 的範例來舉例
execCommand
則是瀏覽器提供開發者透過 js 操作 designMode: "on"
的 Document 或是 contentEditable: "true"
的可編輯元素。它支援了許多功能,幾乎你想得到的所有功能諸如: delete
、 insertHTML
,最常見的 undo
、 redo
,以及像是粗體、斜體等功能也跟前面提到的快捷鍵一樣,會搭配 Web API 提供的 Selection
Object 找出瀏覽器當前的活躍區域與選取範圍並執行操作。
Selection
Object 的 MDN 連結,因為不是本章的重點就先不特別介紹它了這一切明明看似如此美好,理論上我們只要做好一條 toolbar 再另外搭配上 execCommand
,頂多額外加個一層邏輯去處理 api 的使用方式一切就大功告成,不過如果讀者有前去查看 它的 MDN 的話會發現上頭頂著一塊紅色大布條,寫著 " This feature is obsolete (過時的)"
究竟事態是如何逐步發展至如此境地的呢?如此極致方便的功能竟遭人唾棄至此,甚至你 google : contenteditable 關鍵字跳出來的前幾篇文章就出現了一篇標題上寫著 " Why ContentEditable is Terrible " 的 medium 文章。
首先是 execCommand
在各個瀏覽器的支援程度不一,我們可以透過 這份 codepen 去查看當前開啟的瀏覽器支持哪些 execCommand
的指令。
其次它跟 contenteditable
一樣,指令所導致的行為很不受控,在不同的瀏覽器下甚至會產出不同的結果,例如快捷鍵 cmd + b
在 chrome 裡面會插入 <b>
tag ,而在 ie11 中則是增加了 <strong>
tag ,在 safari 裡甚至不支援快捷鍵功能。
除此之外,裡面提供的許多修改樣式的功能,它們的修改方式都是直接修改 tags 的 inline-style ,這顯然也會造成開發人員不少的麻煩,與其使用這些功能然後另外找一堆方法修補造成的洞,那不如從一開始就避免使用這些會造成麻煩的功能,刪完一輪的結果發現根本沒剩多少功能是適合使用的 ?
execCommand
api 的開發經驗讓我們再舉幾個常見,但卻難以透過 contenteditable
實現的例子:
當我們要將上圖裡的段落內容轉換成 DOM 形式的話,讀者心裡的結果可能如下:
The <a href=”http://en.wikipedia.org/wiki/The_Hobbit">hobbit</a> was a very well-to-do hobbit, and his name was <strong><em>Baggins</em></strong>.
但真的只有這樣的編譯結果嗎?我們將目光聚焦在段落尾端的 " Baggins " ,它其實可以有非常多種的編譯形式
<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 所轉換成的,但編輯器應該要讓 『 user 在針對相同的渲染結果使用相同的 api 時,有相同的行為發生』,這也意味著編輯器要有辦法釋讀出不同的 DOM 結構並判斷它們最後的渲染結果是否相同才有辦法達成。
然而這件事實際上卻難以實現,因為可條列出的可能性實在太多種了,即便開發者自認完成了這項功能也難以測試。
但 contenteditable
卻無法保證相同的行為會導致什麼樣的 DOM 輸出結果,在某些時候它可能會插入一些 invisible 的 character ,或是完全 empty 的 span
tag 。
再來是另一個也非常常見的功能:複製貼上。
之所以難以達成這項功能的原因其實與上述的問題癥結點的本質是一樣的,在使用 contenteditable
屬性時,開發者難以預期編輯器當前的 DOM 結構,無法預期的話也就不會知道當前 user 複製的段落究竟包含了哪些 DOM tags 。
在同一個瀏覽器底下實作可能還看不太出問題,但如果今天是跨瀏覽器的開發,開發者就準備頭大了!要處理的 edge cases 數不勝數,堪稱無底洞,根本就不可行。
嗯 ... 我想如果是由我開發一組 WYSIWYG 編輯器的話,我應該還是會先試著去思考這些瀏覽器提供的屬性以及功能有什麼是我能夠取來用的吧?畢竟這開箱即用的效果實在太誘人了!
說得不錯,其實就算是拉回現代,大多數的編輯器與套件都還是一定程度地依賴於瀏覽器提供的功能,團隊們在研發自己的產品時一樣有成本、時間、服務客群等因素上的考量。
而 Web WYSIWYG 這項議題也確實圍繞著 contenteditable
打轉了許久,延伸出了相應不同 世代 的套件類型,緊接著我們就要進入到下個篇章,也就是目前市面上幾個知名的 libraries 相互的比較,但在那之前我們要先花些篇幅去為不同世代的套件們做點分類,了解它們各自有哪些主要的特徵。然後我們才會順著時間線聊聊這些世代底下的 libraries 它們各自所面臨的問題, Slate 的作者最後又是因為什麼理由決定製作 Slate 的。