文章開始前先上個圖:

大話富文本技術概要:
在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

