基於uiwebview富文本編輯器實踐

背景

最近咱們微信讀書將寫想法換成了基於webview的富文本編輯器,遇到了很多問題,這裏我將簡單的介紹一下咱們在開發過程當中踩到的坑。css

實現富文本編輯器有兩個基本思路:html

  1. 基於native實現:好比coretext或者textkitnode

  2. 基於uiwebview實現android

第一種方案,你須要本身去實現不少在webview已經很成熟的效果,好比連接,字體加粗,標題,引用樣式,列表樣式等等,這些的工做量都比較可觀,並且還有ios/android兩個端的對齊問題。還有一個問題,這個多是咱們項目相關的問題,咱們在原來尚未不少富文本要求的狀況下,在textview上作了一些咱們對連接的處理工做,僅僅這一個方面,當時就以爲不是很方便。ios

第二種方案,你能夠藉助webview省掉不少在第一種方案裏面提到的工做,同時webview相對而言,開源的可供參考的項目也更多一點,不過webview也會存在光標的控制,css的衝突處理以及兼容性的問題,不過在最終選擇方案的時候,咱們幾經權衡,最終選擇了webview的方案git

基於webview的富文本編輯器的光標及樣式問題

uiwebview實現富文本編輯器,一個大麻煩在於光標的處理,另外一個大麻煩就是css樣式的兼容,具體體現爲以下幾個方面:github

  1. 如何保持光標在可見區域。web

  2. 插入表情的時候uiwebview會失焦問題。api

  3. 原生命令會有bug,須要本身處理。微信

  4. 樣式的兼容性。

  5. at以及話題的連接處理。

如何保持光標在可見區域

這裏有不少狀況,如咱們在當前可見區域的最後一行的時候,再進行換行時,光標會跑到可見區域的下面,webview不會把光標所在位置自動滾動到可見區域,須要手動觸發webview的滾動機制.

解決這個問題有兩個思路,一種思路就是hack native的滾動邏輯,對滾動進行修正處理,這樣作,能使得webview滾動表現的像native同樣,可是須要hack的地方會比較多,實現起來可能會踩吭。另一種思路就是直接在js裏面操做scrollview,這樣相對比較簡單。咱們使用的是後者的作法,經過監聽光標位置的變化,來進行修正,代碼以下:

document.addEventListener("selectionchange", function(e) {
       RE.calculateEditorHeightWithCaretPosition();//矯正位置
});

這裏矯正的具體邏輯是,每次光標位置發生變化時首先計算當前光標的位置,而後判斷當前光標是否在可見區域範圍內,若是不在,那麼執行window.scrollTo滾動到響應區域。這裏有個小點須要注意,就是在判斷光標位置的時候,頂部以及底部的判斷有稍微的區別,若是判斷光標是在可見區域上面,須要判斷光標的頂部是否在可見區域範圍內,若是斷光標是在可見區域下面,須要判斷光標的底部是否在可見區域範圍內。

這裏還有一個問題,在最後一行,換行到新的一行進行輸入的時候,若是是漢字輸入,會產生聯想輸入條,在尚未肯定輸入內容的時候,uiwebview是不知道你須要的高度的,這個時候,因爲觸發了selectionchange,會致使輸入時候,整個界面不斷的抖動,所以在這裏使用了一個奇技淫巧的方案。咱們監聽input的輸入,並在在webview的最後面,強制插入一個空白的div,使得輸入始終是在已有的區域範圍內的。

另外切記不要在js,以及native兩端都由scroll的操做,這樣會致使滾動的邏輯很混亂。

插入表情,光標的變化

在咱們的場景中,插入表情會彈出一個表情面板(這應該也是主流的作法),這個表情面板會覆蓋鍵盤,這樣會致使uiwebview失焦。這就意味着,咱們在插入表情的時候,不能直接使用Inserthtml命令插入,由於在失焦狀態下這個命令會失效,所以在這裏須要手動找到正在操做的node,手動執行Insertnode的操做,而且要記錄光標的變化位置,以便退出面板退出後,從新foucus找到正確的光標位置。簡單的僞代碼以下代碼

// 這時候光標直接parent是editor,須要判斷光標所在的node的nodeIndex,而後將imgNode 插入到parent[nodeIndex]以前
            if(currentOffset == currentNode.childNodes.length){
                // 光標在editor最後或者editor內容爲空,直接append
                currentNode.appendChild(imgNode);
            }else{
                currentNode.insertBefore(imgNode, currentNode.childNodes[currentOffset]);
            }

而後更新一下當前selection,同時在表情面板刪除表情的時候,也須要作相似的操做。

表情還有一個問題,就是在webview裏面插入表情以後,傳給後臺的時候,由於ios/android兩個平臺的表情本地文件名,樣式有所區別,所以要轉化成[發呆]這個格式。

原生api的坑

前面在提到webview的優勢的時候說過,webview很吸引人的一個地方在於其提供了不少原生的接口來實現同樣樣式,可是,真正操做起來才發現,套路遠比咱們想象的要深。

舉一個簡單的例子你要實現加粗以及取消加粗,那麼一個命令就能搞定。

document.execCommand('bold', false, null);

一樣的,按照文檔的介紹,若是若是你想要操做quote格式,改爲一下命令就行了

document.execCommand('formatBlock', false, "<blockquote>")

然而上面這個命令只能讓你添加引用格式,若是要取消引用格式,你會發現然而並無卵用,就須要本身另外想辦法了。這裏有一個哥們對於quote的悲催經歷 ,不過咱們最後的處理方案和他略有不一樣,咱們是經過判斷當前光標所在的Node是否是有blockquote標籤或者是否是含有blockquote標籤的Node的子node來決定是添加引用仍是取消引用,僞代碼以下:

if (inQuoteBlock.is) {
        document.execCommand('formatBlock', false, "<div>")
    } else {
        document.execCommand('formatBlock', false, "<blockquote>")
        }

一樣的還有標題設置等等都有這樣的問題。

樣式的兼容性

這個問題發生在多個樣式並存的狀況,好比引用、標題、序列格式、高亮連接混在一塊兒用會發現各類奇奇怪怪的問題。

以咱們碰到的一個引用和高亮連接混用的例子來講明。咱們先使用引用格式 blockquote,而後在引用文字裏面插入一個a標籤,接下來再次輸入其餘文字的時候,會發現系統幫咱們偷偷的加了一個span標籤,這個標籤有它本身的style,致使後面樣式跟前面的不一致了。

再舉一個例子,咱們開發的時候測試發現一個bug,就是當引用、標題、序列格式同時運用到一段文字的時候,會發現系統默默的幫你插入了一個div標籤,這個也會致使一些莫名奇妙的格式問題。

固然還有一些其餘奇奇怪怪的問題,這些實際上是css樣式的問題,對於這些問題的處理,要麼本身維護一套css格式庫,而後不要使用系統的document.execCommand命令,本身去封裝,這個固然是最完全的,可是也是最費工做量的,另一個方法就是去限定某些組合的可能性,或者對某些場景的場景進行特殊處理,固然這個只是不補救的方案啦,具體怎麼作,取決於使用場景,畢竟咱們不是作一個word,因此未必須要考慮的那麼全面。

at以及話題的連接處理

其實這裏就是對webview的連接處理問題了。以@爲例,咱們的需求要求點擊@以後,生成一個搜索框,可以搜索想要@的用戶,若是使用textview,咱們徹底可以在textView的delegate裏面,根據當前的位置以及輸入的內容進行搜索,可是webview你是很難去獲取用戶一段時間輸入的內容的,所以,咱們直接使用連接代替,當輸入一個@以後就生成一個連接,而後搜索操做就在連接中進行(這裏有個小技巧,若是隻是輸入一個@字符而後將其變成一個Link,那麼光標默認的會處在link的外面,所以接下來的輸入,不會成爲連接的一部分,所以在這裏咱們生成的是一個 "@ "加一個空格的連接,並把光標手動移動到@以後)。不過這裏還有一個光標的坑,由於咱們選擇一個用戶以後須要替換掉已經輸入的部分,也就是將link內容替換掉,會發現光標會移動到link的最前面去,光標又亂跳了!因此其實這裏還須要本身去移動光標!

另外這裏在進行搜索的時候還有個問題,就是在使用系統輸入法輸入中文的時候,會出現聯想輸入條(quicktype),若是這個時候,用戶沒有選擇輸入條的內容,而是直接選擇了用戶名進行替換,那麼咱們會自動將當前的link替換成選擇後的內容,並將光標移動到Link的後面,可是這個時候,其實系統輸入法的聯想輸入還沒結束,所以當用戶再次點擊輸入的時候,系統會默認找原來開始聯想輸入時候的Node位置,可是因爲這個已經被咱們替換掉了,會找不到,從而使得光標跑到webview的外面去,所以咱們還須要在這裏經過監聽compositionupdate,進行修正光標的位置

總結

總得來講,基於webview的富文本,雖然系統幫咱們作了不少事情,可是真正實踐起來仍是會發現問題遠比咱們想象的多,因此永遠不要懷疑word開發那麼多年的工做!另外要基於webview作富文本編輯器,那麼必定要對Js有必定的瞭解,要否則會發現很鬼頭痛!不過對於大多數app而言,其實咱們的要求是沒那麼高的,因此找一個適合本身的webview的開源方案仍是能很大的減小本身的工做量。

相關文章
相關標籤/搜索