文章開始前先上個圖:
大話富文字技術概要:
在web領域,一提到富文字,大夥都覺得很高深,很難,很複雜。但是如果你看了我這篇簡短的技術分析,你會發現其實富文字不算高深,稱不上很難,只是比較複雜,需要用點心,折騰幾回你也能做一個富文字編輯器。下面我將採用“問題+答疑”方式聊聊web端富文字。
問題一、富文字是怎麼形成的?
有網友在切換【原始碼】狀態下看到一大堆html + css修飾時,竟然向我提問:“怎麼是這樣的一堆東西?”。網友很驚訝,我也很驚訝。作為一個web開發者,寫不了富文字,那也不至於不瞭解web富文字的構成吧!所以我覺得有必要宣告一下這個基礎知識:web富文字是由html標籤 + css修飾形成的 !君不信,可以去翻看ueditor、tinyMCE、kingEditor等等富文字編輯器。
問題二、富文字既然是html + css修飾形成的,那怎麼做才能根據使用者操作進行修飾呢?
回答這個問題,需要從兩方面來解析:
1、JavaScript指令碼是如何知道使用者游標所在的選區?
答案:range物件。
每個瀏覽器中的window物件之下都存在一個range物件。range物件存放著當前游標所在的選區資訊。現代瀏覽器(IE10、chrome、firefox)對range的支援都很良好,如果是舊版本的ie瀏覽器,range物件的獲取,其內部的某些api可能有不同的差異,需要做相容性處理。根據range物件獲得選區資訊是開發者操控選區html內容的第一個步驟。
2、得到了選區後,如何進行css修飾,比如修飾color=“red”?
這可以說是富文字技術實現的麻煩之處(難點)。玩過富文字技術的同學,肯定會想到“document.execCommand()”這個大名鼎鼎的物件。目前市面上的富文字基本都是基於這個物件打造。比如對選區執行一個加粗修飾,你可以直接呼叫“document.execCommand('bold')”。該API會將使用者選擇的文字都加上一個"strong"標籤。
然而,document.execCommand不是萬能的,比如不支援插入檔案、視訊,行高、邊距修飾等。所以基於這個api的富文字都必須擴充套件execCommand介面。execCommand除了支援有限外,還有個很令人不爽的地方,其執行的修飾並不符合web規範的要求,比如加粗採用“strong”標籤,而不是css修飾裡的“font-weight:bold”,而且多次修飾操作會產生N層巢狀。基於execCommand打造的富文字可以說沒有規律而言。其輸出的html內容,不適用於後端轉word、pdf等需求場景。
問題三、execCommand是富文字技術的核心,但也是周身缺點,請問有何良方?
良方:依靠range,提取使用者選擇的文字,將文字採用span標籤包裝,然後採用標準的css對span標籤進行修飾!
問題四、良方思想很好,實現上有什麼難點嗎?
難點肯定有,而且需要一些巧妙的設計及實現。
1、選區丟失問題
選區丟失問題,execCommand方案同樣存在。表現為:使用者劃選了選區,當點選修飾按鈕時候,由於瀏覽器的滑鼠焦點機制,選區丟失了,造成點選事件的修飾功能找不到選區。
解決辦法:利用編輯器區域的mouseleave,修飾按鈕的mousedown、mouseup組合應用對選區進行暫存和恢復。
2、span標籤組裝問題
span標籤組裝?是什麼意思呢?請看下面一個選區demo,使用者劃選的內容是跨元素節點的複雜選區。
使用者劃選的區域,覆蓋了前後兩個span、中間一個純文字。根據修飾需求,需要對 “節點中間內容結束” 這個劃選的內容進行加粗。那麼需要將上述選區轉為span包裝後利用css修飾:
從上述demo,可以看出使用者劃選的內容可能是一個標籤內的,也可能是跨多個標籤而形成的。開發者需要編寫一個演算法將劃選內容提取組裝為多個span水平包裝的結構。
3、span水平包裝演算法實現要點
根據range物件的collapsed屬性判斷是否是跨標籤選區。
1)非跨標籤選區:根據range.startOffset、range.endOffset拆分內容形成一個陣列,然後對每個陣列的內容進行span包裝。
2)跨標籤選區:根據range.startContainer、range.endContainer、range.startOffset、range.endOffset,將內容進行拆分,並將拆分後的內容進行span包裝。
4、span包裝引起的選區丟失恢復問題
在span包裝的演算法中,由於需要對dom節點進行刪除、插入,會引起選區丟失。故需要在進行span組裝前,將當期選區的資訊暫存起來,組裝好span後,根據暫存資訊進行選區恢復。
演算法思想總結
1、利用range物件獲取使用者選區資訊。
2、根據range資訊,將使用者選擇的內容進行拆分組合,形成水平結構的span包裝。
3、上述演算法中,由於焦點變化,dom刪除插入的影響,存在選區丟失的問題,需要將選區資訊暫存,並在適當時候恢復選區。
特色功能設計實現思想
在bui-editor富文字中,提供了比較有特色的 “浮動文字、圖片”,左右邊距拖動調整,流程圖繪製等特色功能。下面介紹一下這些特色功能的實現。
浮動文字/圖片功能
1.web開發者都知道,html的浮動是利用position:absolute來實現的,absolute要求父元素是relatvie或者absulote。bui-editor同樣是利用這個技術點來實現浮動文字/圖片需求。
2.bui-editor中會將編輯區域用一個宣告瞭relative的div進行包裝,在這個包裝div之下,是存放段落的div和浮動的div,這樣從結構設計上滿足段落的流式佈局,又滿足了浮動的需求。
3.編輯區域內既然已經存在了流式段落div和浮動div,那麼需要對這兩種不同的div內容分別做處理。
左右邊距拖動調整功能
邊距拖動:利用range選區提取當前覆蓋的段落,通過拖動兩邊的邊線,調整段落的margin-left、width進行實現段落左右邊距的調整。
流程圖繪製功能
流程圖功能:利用bui-flow設計器繪製好流程,通過canvas技術匯出base64位的圖片資料,然後插入到富文字中。富文字中將流程圖的json資料儲存起來並實現流程圖的可編輯。
富文字結構設計
目前市面上的富文字幾乎都是利用execCommand api來實現,這個api輸出的富文字html結構是混亂的(標籤不規範、N層巢狀、非css修飾)。這樣的富文字結構限制了富文字應用的後端擴充套件,難以實現word、pdf的轉換。為此,bui-editor對富文字的輸出結構做了規範化的定義:
1、段落結構採用div標籤,為什麼不採用p標籤呢?p標籤是一個內容標籤,而我們的段落內還存在table(表格),採用p標籤不符合w3c規範了。
2、段落內採用水平化的span子標籤,利用span子標籤包裝內容,這樣便於將修飾設定到span標籤上,同時水平化的結構避免了巢狀的問題。
3、段落div內除了span子標籤,還存在table標籤、image標籤、pre程式碼塊標籤的可能性。
4、table單元格內採用p標籤作為單元格內的基本輸入單位,這樣可以解決單元格內回車換行的需求。
技術要點總結:
1、掌握range物件
2、理解選區丟失的場景、原因,及其對應策略
2、實現span拆分包裝演算法
4、設計好富文字的html結構,拋棄execCommand Api
我相信,如果你對上述要點都有了理解,只要你願意多動幾次手,開發富文字不是個很難的事情。
歡迎訪問專案: https://gitee.com/kevin-huang/Bui-Editor-public
歡迎訪問我正在開發的流程設計器demo : http://www.vvui.net/flow/index.html