文章開始前先上個圖:css
大話富文本技術概要:html
在web領域,一提到富文本,大夥都以爲很高深,很難,很複雜。可是若是你看了我這篇簡短的技術分析,你會發現其實富文本不算高深,稱不上很難,只是比較複雜,須要用點心,折騰幾次你也能作一個富文本編輯器。下面我將採用「問題+答疑」方式聊聊web端富文本。git
問題1、富文本是怎麼造成的?web
有網友在切換【源碼】狀態下看到一大堆html + css修飾時,居然向我提問:「怎麼是這樣的一堆東西?」。網友很驚訝,我也很驚訝。做爲一個web開發者,寫不了富文本,那也不至於不瞭解web富文本的構成吧!因此我以爲有必要聲明一下這個基礎知識:web富文本是由html標籤 + css修飾造成的 !君不信,能夠去翻看ueditor、tinyMCE、kingEditor等等富文本編輯器。算法
問題2、富文本既然是html + css修飾造成的,那怎麼作才能根據用戶操做進行修飾呢?chrome
回答這個問題,須要從兩方面來解析:json
一、JavaScript腳本是如何知道用戶光標所在的選區?canvas
答案:range對象。後端
每一個瀏覽器中的window對象之下都存在一個range對象。range對象存放着當前光標所在的選區信息。現代瀏覽器(IE十、chrome、firefox)對range的支持都很良好,若是是舊版本的ie瀏覽器,range對象的獲取,其內部的某些api可能有不一樣的差別,須要作兼容性處理。根據range對象得到選區信息是開發者操控選區html內容的第一個步驟。api
二、獲得了選區後,如何進行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等需求場景。
問題3、execCommand是富文本技術的核心,但也是周身缺點,請問有何良方?
良方:依靠range,提取用戶選擇的文本,將文本採用span標籤包裝,而後採用標準的css對span標籤進行修飾!
問題4、良方思想很好,實現上有什麼難點嗎?
難點確定有,並且須要一些巧妙的設計及實現。
一、選區丟失問題
選區丟失問題,execCommand方案一樣存在。表現爲:用戶劃選了選區,當點擊修飾按鈕時候,因爲瀏覽器的鼠標焦點機制,選區丟失了,形成點擊事件的修飾功能找不到選區。
解決辦法:利用編輯器區域的mouseleave,修飾按鈕的mousedown、mouseup組合應用對選區進行暫存和恢復。
二、span標籤組裝問題
span標籤組裝?是什麼意思呢?請看下面一個選區demo,用戶劃選的內容是跨元素節點的複雜選區。
用戶劃選的區域,覆蓋了先後兩個span、中間一個純文本。根據修飾需求,須要對 「節點中間內容結束」 這個劃選的內容進行加粗。那麼須要將上述選區轉爲span包裝後利用css修飾:
從上述demo,能夠看出用戶劃選的內容多是一個標籤內的,也多是跨多個標籤而造成的。開發者須要編寫一個算法將劃選內容提取組裝爲多個span水平包裝的結構。
三、span水平包裝算法實現要點
根據range對象的collapsed屬性判斷是不是跨標籤選區。
1)非跨標籤選區:根據range.startOffset、range.endOffset拆份內容造成一個數組,而後對每一個數組的內容進行span包裝。
2)跨標籤選區:根據range.startContainer、range.endContainer、range.startOffset、range.endOffset,將內容進行拆分,並將拆分後的內容進行span包裝。
四、span包裝引發的選區丟失恢復問題
在span包裝的算法中,因爲須要對dom節點進行刪除、插入,會引發選區丟失。故須要在進行span組裝前,將當期選區的信息暫存起來,組裝好span後,根據暫存信息進行選區恢復。
算法思想總結
一、利用range對象獲取用戶選區信息。
二、根據range信息,將用戶選擇的內容進行拆分組合,造成水平結構的span包裝。
三、上述算法中,因爲焦點變化,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對富文本的輸出結構作了規範化的定義:
一、段落結構採用div標籤,爲何不採用p標籤呢?p標籤是一個內容標籤,而咱們的段落內還存在table(表格),採用p標籤不符合w3c規範了。
二、段落內採用水平化的span子標籤,利用span子標籤包裝內容,這樣便於將修飾設置到span標籤上,同時水平化的結構避免了嵌套的問題。
三、段落div內除了span子標籤,還存在table標籤、image標籤、pre代碼塊標籤的可能性。
四、table單元格內採用p標籤做爲單元格內的基本輸入單位,這樣能夠解決單元格內回車換行的需求。
技術要點總結:
一、掌握range對象
二、理解選區丟失的場景、緣由,及其對應策略
二、實現span拆分包裝算法
四、設計好富文本的html結構,拋棄execCommand Api
我相信,若是你對上述要點都有了理解,只要你願意多動幾回手,開發富文本不是個很難的事情。
歡迎訪問項目: https://gitee.com/kevin-huang/Bui-Editor-public
歡迎訪問我正在開發的流程設計器demo : http://www.vvui.net/flow/index.html