wangEditor源碼部分解讀

最近在做富文本相關的探索,看了下wangEditor和draft.js,draft.js相對複雜一些吧,wangEditor的代碼就比較直白,註釋比較多,比較適合閱讀,大體討論一下wangEditor的結構。版本是v3.1.1,講下大概原理,不會每個功能都講。

先看使用方式,

var E = require('wangeditor');

var editor = new E('#editor');
editor.create()

// 自己定義分開的工具欄和編輯區
var editor1 = new E('#div1', '#div2')

代碼大體目錄結構如下:

  1. /src/js/editor中是編輯器的構造函數和一些初始化

  2. /src/js/util中是各種輔助功能,其中有一個dom-core.js文件,封裝了一些dom方法並導出爲$,可以稍微看一下,避免後面看其他代碼被原生方法和封裝的方法弄混

  3. /src/js/command中封裝了document的execCommand、queryCommandValue、queryCommandState、queryCommandSupported等方法,是改變編輯區內文本樣式的核心方法

  4. /src/js/menus中是支持的功能的實現,比如加粗、斜體、字體顏色等

  5. /src/js/selection中封裝了和選區有關的方法,也屬於核心方法

  6. /src/js/text中是編輯區的事件綁定,及文本內容相關的代碼

原理

主要原理是使用contenteditable屬性,配合selection、range、execCommand等API,實現富文本編輯功能,這裏的富文本是指html標籤和css樣式。

實現

我們先來看/src/js/editor/index.js,首先定義了一個editorId變量用來維護一個頁面上存在多個編輯器的情況。構造函數本身沒幹什麼事情,記錄了toolbarSelector、textSelector這兩個變量,分別是工具欄和編輯區的選擇器,構造出來以後調用create函數,看一眼create函數幹了什麼,一堆的函數調用,挨個進去看。

  1. _initConfig,把用戶自定義的設置項和默認設置項merge好,
  2. _initDom,把編輯區和工具欄的dom創建出來,還處理了一些事件,比如輸入法的開始和結束事件,焦點事件,其中處理了點擊工具欄時,不認爲失去編輯區的焦點這一邏輯,這個函數裏掛到editor實例上的變量有$toolbarElem工具欄dom, $textContainerElem編輯區容器, $textElem編輯區dom, toolbarElemId工具欄dom的id, textElemId編輯區dom的id,可以看到變量比較多比較亂,對後面理解相關代碼也造成了一定麻煩
  3. _initCommand、_initSelectionAPI、_initUploadImg、_initMenus、_initText這些函數都是將相應的模塊創建出來,看到具體的模塊時再說,這些模塊都掛到了editor的實例上面,有this.cmd、this.selection、this.uploadImg、this.menus、this.txt這些
  4. initSelection,初始化選區,將光標定位到內容尾部(代碼註釋原文)
  5. _bindEvent,綁定事件,包含節流的this.change,還有一些事件是通過config引入的

接下來看看各個模塊分別做了哪些事情。所有模塊都會在構造函數中把editor實例引入。

selection

獨立的模塊,不依賴其他模塊,有一個_currentRange變量,保存了當前的選區,並提供了getRange函數,來將當前選區暴露給其他模塊使用。另外還有如下函數:

  1. saveRange,如果選區在編輯區內的,則將選中的區域設置爲_currentRange
  2. collapseRange摺疊選區
  3. getSelectionText獲取選區內容並轉換爲文字
  4. getSelectionContainerElem返回選區內的元素節點
  5. getSelectionStartElem、getSelectionEndElem獲取選區中起始和結束的元素節點
  6. isSelectionEmpty,判斷選區是否爲空
  7. restoreSelection,把當前選區裏的range都移除,並設置爲_currentRange
  8. createEmptyRange,創建一個空的選區
  9. createRangeByElem,使用元素創建選區

如果不瞭解selection和range API的話,建議先看一看文檔

command

同樣是獨立的模塊,command模塊的函數有如下這些:

  1. do,包含自定義事件和瀏覽器支持的命令的執行,執行完畢命令後立刻更新菜單區域的狀態,並調用selection模塊的saveRange和restoreSelection函數,這兩個函數會多次遇到,並且多是成對出現,最後觸發editor的change函數
  2. _insertHTML_insertElem,插入html和節點
  3. _execCommand、queryCommandValue、queryCommandState、queryCommandSupported,對document對應方法的包裝

text

除了菜單的內容外,主要的編輯器的邏輯,比editor的代碼還多

init函數調用了_bindEvent函數來綁定事件,和editor裏的風格一樣,一堆事件,

  1. _saveRangeRealTime內部寫了一個saveRange函數,並綁定了keyup、mousedown、mouseup事件,mousedown和mouseup主要是用來處理在編輯區域內按下鼠標,然後移動到了編輯區域外的情況

  2. _enterKeyHandle按下回車時的處理,keydown綁定了對code標籤的處理,主要是在code標籤中連續回車兩次的話,就跳出code塊,keyup綁定了對p標籤的處理,主要處理了回車換行插入一個p標籤

  3. _clearHandle,主要處理按下backspace時的事件,因爲使用的是div標籤作爲編輯器的根節點,所以保留一個p標籤作爲新創建的一行,如果沒有這個p標籤,回車創建出來的都是div標籤,這個特性應該是瀏覽器的實現

  4. _pasteHandle,處理粘貼事件,裏面有個canDo比較奇怪,定時100ms來判斷是否可以執行粘貼,獲取粘貼的內容的函數都在util/paste-handle.js裏

  5. _dragHandle,拖曳事件,如果拖曳的內容包含文件,調用上傳函數

看到這裏爲止其實大部分實現我們都已經看完了,你可能覺得我說了一堆廢話,因爲很多內容代碼的註釋裏都是有的,並且說的零零碎碎,不成體系,那麼我們來梳理一下,當你使用富文本編輯器的時候,會有哪些操作,首先肯定是輸入文本,然後選中了其中的某一段,點擊了加粗或者斜體或者顏色等菜單,然後被選中的那些文本按照菜單功能展示了,然後可能還有插入圖片,插入鏈接這些稍微高級一些的操作,還有複製粘貼這些操作等。

文本的輸入由contenteditable屬性處理了,我們註冊了一些事件處理一些比較特別的按鍵,比如回車和刪除,都在text裏;選擇某段文字,這個在selection裏;加粗、斜體、顏色這些,一部分在command裏,一部分在menus裏,接下來我們來看menus。

menus

menus裏有很多文件夾,backColor、bold等等,都是editor所支持的菜單功能,在menu-list.js裏引用了全部這些菜單項(😂),index裏引用了menu-list,把菜單項一個個渲染出來,並綁定點擊事件,這麼說完全沒感覺,來看一個菜單項,backColor,構造函數裏創建了一個droplist用來選擇顏色,然後onClick裏調用_command方法,_command裏執行的就是execCommand,感覺還是沒說什麼(😂)

總結

這篇源碼解析怎麼說呢,寫得有點難受,感覺一直在說些沒必要說的話,自己去看的話,很多邏輯註釋裏都寫了,但是如果不說呢,第一次看又會稍微有點點亂,因爲各個模塊之間並沒有太強的關聯,也沒有什麼流程,而是以事件觸發的形式來觸發各種處理函數,這篇文章寫的也只能作爲一個看過這個源碼的記錄而已。如果真的要用它,還是自己去看一看

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章