一塊兒動手擼一個富文本編輯器吧

前言

公司要作一個筆記模塊,須要用到富文本編輯器。以前有耳聞富文本編輯器是天坑。知乎-爲何說富文本編輯器是個天坑? 在試過了市面上主流的編輯器後,發現或多或少都不符合要求。主要有如下問題:javascript

  1. CKEditor功能很強大,可是太複雜,有不少用不到的地方。
  2. 項目前端框架是Vue,最好是基於Vue2.x的編輯器
  3. 網上開源的編輯器體驗或多或少有不知足的地方。

還好開發時間比較富足,因而決定在vue-html5-editor基礎上二次開發,最後完成上線的做品,呼喚star✨ 🙋 Github:my-vue-editorhtml

實現套路

web端實現富文本編輯器主要有2個套路:前端

  1. 利用contenteditable屬性結合document.execCommand API實現,好比國外的CKEditor、百度的UEditor、優秀的後起之秀wangEditor。
  2. 徹底本身模擬實現selection、視圖渲染等一切。好比Google Doc、有道雲筆記、基於electron開發的VS Code。

這裏咱們很理智的選擇了第一種實現方式。先簡單介紹下編輯器很重要的幾個概念:vue

Range/Selection

Range 翻譯過來是範圍,幅度的意思,與數學上的「區間」這以概念相似。瀏覽器提供的Range對象用來描述DOM樹中的一段連續的範圍。html5

startContainerstartOffset描述Range的起始處,endContainerendOffset描述Range的結尾處。當一個Range的起始處和結尾處是同一個位置時,該Range就處於collapsed狀態。java

Selection(選區)管理整個頁面當前的RangeRange的繪製。當Selection中的Range處於collapsed狀態時,便是平常所說的光標。光標實際上是Selection的一種特殊狀態。node

document.execCommand

瀏覽器原生爲咱們提供了一些對Range內節點進行富文本操做的方法,這些方法都是經過document.execCommand調用。git

bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument)
複製代碼

好比github

// 向當前插入點插入一個p標籤。
document.execCommand('insertHTML', false, '<p></p>')

// 將框選部分字體變爲綠色,若是是collapsep狀態則接下來輸入的文字爲綠色
document.execCommand('foreColor', false, '#00ff00')
複製代碼

咱們編輯器的架都是圍繞這兩個概念展開的:web

  1. 當咱們點擊編輯器各類功能按鈕(好比插入圖片、加粗、下劃線)時內容區域會失去焦點,因此咱們須要一種可以保存當前Range對象,並在須要時能夠調用的機制。
  2. 咱們已經知道document.execCommand是用來操縱選區HTML結構的,可是原生提供的方法的邏輯大多數都不徹底符合咱們的須要,或者存在兼容性問題。因此咱們封裝咱們本身的構造函數Command用來操縱富文本,不一樣的按鈕點擊後就會實例化相應的Command並執行相關操做。

對於第一點,只須要定義一個保存,一個設置方法。

// 保存當前Range
function saveCurrentRange () {
    // 獲取selection對象
    const selection = window.getSelection ? window.getSelection() : document.getSelection()
      if (!selection.rangeCount) {
        return
      }
      const content = this.$refs.content
      for (let i = 0; i < selection.rangeCount; i++) {
        // 從selection中獲取第一個Range對象
        const range = selection.getRangeAt(0)
        let start = range.startContainer
        let end = range.endContainer
        // 兼容IE11 node.contains(textNode) 永遠 return false的bug
        start = start.nodeType === Node.TEXT_NODE ? start.parentNode : start
        end = end.nodeType === Node.TEXT_NODE ? end.parentNode : end
        if (content.contains(start) && content.contains(end)) {
        // Range對象被保存在this.range 
          this.range = range
          break
        }
      }
}

// 設置Range對象
function restoreSelection () {
    // 首先獲取selection對象並清除當前的Range
      const selection = window.getSelection ? window.getSelection() : document.getSelection()
      selection.removeAllRanges()
      // 從this.range中得到保存的Range設置爲Selection的Range對象
      if (this.range) {
        selection.addRange(this.range)
      } else {
        // 若是以前沒有保存Range則新建一個
        const content = this.$refs.content
        const row = RH.prototype.newRow({br: true})
        const range = document.createRange()
        content.appendChild(row)
        range.setStart(row, 0)
        range.setEnd(row, 0)
        selection.addRange(range)
        this.range = range
      }
    }
複製代碼

有了這兩個方法,咱們只須要爲編輯器的內容區域註冊mouseup keyup mouseout事件監聽來實時執行saveCurrentRange,當點擊按鈕後在實例化Command前執行restoreSelection

對於第二點,封裝execCommand方法很好理解,好比我要實現"縮進indent"的功能,document.execCommand 就提供了indent這個參數能夠直接使用,當Range處於ul>li,中執行indent會讓ul嵌套ul,變成ul>ul>li,多個縮進就執行多個嵌套。這知足咱們的須要。

// 縮進前
<ul>
    <li>當前光標位置</li>
</ul>

// 縮進後
<ul>
    <ul>
        <li>當前光標位置</li>
    </ul>
</ul>
複製代碼

可是當Range處於通常的塊級元素中,執行indent會讓塊級元素外面嵌套blockquote元素,咱們想經過在塊級元素上增長margin-left來處理通常塊級元素的縮進。

// 縮進前
<p>當前光標位置</p>

// 縮進後
<blockquote>
    <p>當前光標位置</p>
</blockquote>

// 咱們但願的狀況
<p style='margin-left: 8%;'>當前光標位置</p>
複製代碼

咱們只須要封裝execCommand方法,當其參數爲indent時,執行對應封裝好的indent方法,判斷Range是處於列表元素仍是其餘塊級元素中分別對待就行。 這裏之因此要採用構造函數而不是普通函數的形式,是由於全部原生的execCommand方法,當執行時瀏覽器內部會對該contenteditable區域維護一個undo棧和一個redo棧,使得每個修改行爲能夠撤銷和重作。

咱們封裝的方法覆寫了原生的方法,就會破壞undo/redo棧的連續性,致使撤銷和重作出錯或失效。因此咱們須要在每一個Command實例上保存執行前編輯器區域的DOM結構(快照)和執行後編輯器區域的DOM結構(快照),並把這個實例推入相應的undo/redo棧。當咱們執行撤銷和重作操做時只須要從相應的棧中取出保存的快照恢復到內容區域便可。 因此你發現啦,undoredo也是兩個須要重寫的Command

到這裏一個富文本編輯器的雛形就出來了,咱們只須要在這個基礎上不斷完善咱們的Command,再處理須要過濾的樣式、多端數據結構同步、各類瀏覽器的兼容性等一個又一個坑就能作出功能豐富的編輯器啦。👏👏👏😄

都看到這裏啦,來試試咱們的編輯器吧,Github:my-vue-editor 以爲好用給個star唄老鐵

相關文章
相關標籤/搜索