11.15 富文本原理了解一下?

前置知識

好了,交代完了背景,讓我們先補充一些基礎知識吧,不懂的請務必不要跳過!

我自己是一名從事了多年開發的web前端老程序員,目前辭職在做自己的web前端私人定製課程,今年年初我花了一個月整理了一份最適合2019年學習的web前端學習乾貨,各種框架都有整理,送給每一位前端小夥伴,想要獲取的可以關注我的頭條號並在後臺私信我:前端,即可免費獲取。

contenteditable 屬性

假如我們給一個標籤加上 contenteditable="true" 的屬性,就像這樣:


那麼在這個 div 中我們就可以對其進行任意編輯了。如果想要插入的子節點不可編輯,我們只需要把子節點的屬性設置為 contenteditable="false" 即可,就像這樣:





該屬性最早是在 IE 上實現的(厲害哦),且可以作用於其它標籤,不限於 div,大家應該或多或少都聽說過這個屬性。

document.execCommand 方法

既然我們可以對上面的 div 隨意編輯,那具體怎麼編輯呢,目前好像也還是隻能輸入文本,要怎樣才能進行其他操作呢(比如加粗、傾斜、插入圖片等等)?其實瀏覽器給我們提供了這樣的一個方法 document.execCommand,通過它我們就能夠操縱上面的可編輯區。具體語法如下:

富文本原理了解一下?

其中第一個參數就是一些命令名稱,具體的可以查看 MDN;第二個參數寫死為 false 就行了,因為早前 IE 有這樣一個參數,為了兼容吧,不過這個參數在現代瀏覽器中是沒有影響的;第三個參數就是一些命令可能需要額外的參數,比如插入圖片就要多傳個 url 或 base64 的參數,沒有的話傳個 null 就行。

我們簡要列舉下它的幾個使用方式,大家就知道怎麼用了:

富文本原理了解一下?

這個命令就是富文本的核心(所以務必記住),瀏覽器把大部分我們能想到的功能也都實現了,當然各瀏覽器之間還是有差異的,這裡就不考慮了。

Selection 和 Range 對象

我們在執行 document.execCommand 這個命令之前首先要知道對誰執行,所以這裡會有一個選區的概念,也就是 Selection 對象,它用來表示用戶選擇的範圍或光標位置(光標可以看做是範圍重合的特殊狀態),一個頁面用戶可能選擇多個範圍(比如 Firefox)。也就是說 Selection 包含一個或多個 Range 對象(Selection 可以說是 Range 的集合),當然對於富文本編輯器來說,一般情況下,我們只會有一個選擇區域,也就是一個 Range 對象,事實上大部分情況也是如此。
所以通常我們可以用 let range = window.getSelection().getRangeAt(0) 來獲取選中的內容信息(getRangeAt 接受一個索引值,因為會有多個 Range,而現在只有一個,所以寫0)。
看得一頭霧水?沒關係,看下面兩張圖就懂了:

富文本原理了解一下?

富文本原理了解一下?

一句話說就是:通過上面那句命令我們能夠獲取到當前的選中信息,一般會先保存下來,然後在需要的時候還原。此外 Selection 對象還有幾個常用的方法,addRange、removeAllRanges、collapse 和 collapseToEnd 等等。
這個知識點是很重要的,因為它讓我們有了操縱光標的能力(比如插入內容之後設置光標的位置),不過這篇文章中我並沒有去深入它,只是淺出。

目標

開篇一頓扯,下面讓我們抓緊時間做一個屬於自己的富文本吧,大概會包含以下幾個功能:加粗、段落、H1、水平線、無序列表、插入鏈接、插入圖片、後退一步、向前一步等等。,Let's do it!

起步

首先一個富文本大體分為兩個區域,一個是按鈕區,一個是編輯區。所以它的大致結構就像下面這樣:

富文本原理了解一下?

富文本原理了解一下?

嗯,起步工作到此結束,接下來就可以直接開始實現功能了。

加粗

現在假如我們要實現加粗的效果,該怎麼做呢?很簡單,只要在點擊加粗按鈕的時候執行 document.execCommand('bold', false, null) 這句話,就能達到加粗的效果,就像下面這樣:

富文本原理了解一下?

讓我們運行一下看看效果:

富文本原理了解一下?

嗯,是的,就是這麼簡單的一句話就能搞定。

當然了,我們開篇也說了我們的一切命令都是基於 document.execCommand 的,所以我們先小小改寫一下上面代碼中的 execCommand 方法,就像下面這樣:

富文本原理了解一下?

這樣一來代碼就更具通用性了。實現列表、水平線、前進、後退功能和加粗是一樣樣的,只需傳入不同的命令名即可,就像下面這樣,這裡就不一一贅述了:

富文本原理了解一下?

順帶給大家說幾個注意點✍️:

1. 有的同學可能用的不是 button 標籤,然後執行命令就會無效,是因為點擊其他標籤大多都會造成先失去焦點(或者不知不覺就突然失去焦點了),再執行點擊事件,此時沒有選區或光標所以會沒有效果,這點要留意一下。

2. 我們執行的是原生的 document.execCommand 方法,瀏覽器自身會對 contenteditable 這個可編輯區維護一個 undo 棧和一個 redo 棧,所以我們才能執行前進和後退的操作,如果我們改寫了原生方法,就會破壞原有的棧結構,這時就需要自己去維護,那就麻煩了。

3. style 裡面如果加上 scope 的話,裡面的樣式對編輯區的內容是不生效的,因為編輯區裡面是後來才創建的元素,所以要麼刪了 scope,要麼用 /deep/ 解決(Vue 是這樣)。

段落

這個功能就是把光標所在行的文字用 p 標籤包裹起來,為了演示方便,我們順便把編輯區的 html 結構打印出來,所以讓我們稍微改一下代碼,就像下面這樣:

富文本原理了解一下?

運行效果如下:

富文本原理了解一下?

怎麼樣,是不是也很 easy,同理,h1 ~ h6 也是一樣的,命令為 execCommand('formatBlock', '

'),也不贅述了。

插入鏈接

這個功能因為需要第三個參數,所以我們一般會給個提示框獲取用戶輸入,然後再執行 execCommand('createLink', 鏈接地址),代碼如下:

富文本原理了解一下?

效果如下:

富文本原理了解一下?

插入圖片鏈接也是異曲同工,只不過命令名不一樣而已:

富文本原理了解一下?

插入圖片

圖片除了可以通過添加地址的形式外,還可以添加 base64 格式的圖片,這裡我們通過 readAsDataURL(file) 來讀取圖片,並執行 execCommand('insertImage', base64) 就大功告成啦,具體代碼如下,並不複雜:

<button>插入圖片  
/<button>
insertImg(e) { let reader = new FileReader(); let file = e.target.files[0]; reader.onload = () => { let base64Img = reader.result; this.execCommand('insertImage', base64Img); document.querySelector('.nav__img input').value = ''; // 解決同一張圖片上傳無效的問題 }; reader.readAsDataURL(file);}

運行一下,看看效果:

富文本原理了解一下?

這應該也不是很難。當然了,你也可以先上傳到服務器處理返回 url 地址再插入也是可以的。
至此,一個簡易版的富文本就完成了(當然了 bug 也是有的,不過並不妨礙我們理解),具體代碼可以參考 npm 上的 pell 包,它已經是個極簡版的了。

進階

其實富文本對文本的操作大多都可以用原生命令來實現,但是對圖片的操作也許就不那麼容易了,來個拉伸、旋轉啥的就夠我們折騰了,所以這裡以圖片拉伸為例子著重講解一下。

圖片拉伸

我們先看下大致效果,大家也可以先停下來思考一分鐘看看如何實現:

富文本原理了解一下?

,首先我們要知道的是圖片已經在編輯區了,所以當用戶點擊編輯區裡面的圖片時我們需要做些事件監聽並有所處理,具體思路如下(這部分代碼較多,不想看的可以略過,但標題要看):

1. 判斷用戶點擊的是否是編輯區裡面的圖片

這個就是看點擊事件 e.target.tagName 是不是 img 標籤了,代碼如下,應該比較簡單:

富文本原理了解一下?

2. 在點擊的圖片上創建個大小一樣的 div

如果點擊的是一個圖片,那我們就創建一個 div ,暫且把這個 div 叫做蒙層吧,順便先看張示意圖:

富文本原理了解一下?

也就是動態創建一個蒙層(和圖片一樣大小)以及四個拖拽頂點,並定位到和圖片一樣的位置,代碼如下(代碼有點多,可跳過,知道大致意思就行):

富文本原理了解一下?

富文本原理了解一下?

3. 在四個頂點框上添加拖拽事件

這裡我們會在四個頂點監聽 mousedown 事件,按下鼠標時,首先會改變鼠標樣式(就是鼠標會變成調整大小的那種圖標),然後監聽 mousemove 和 mouseup 事件,計算出水平拖拽距離,然後重新設置圖片大小和浮層大小,大概這麼個意思,簡要代碼如下:

富文本原理了解一下?

當然問題還是有的,不過我們知道這個思路就行。具體代碼可以去看下 npm 上的 quill-image-resize-module 包,我也是按照這個包的思路來講解的。。。

操縱光標

除了不好對圖片進行處理外,光標應該也是一大坑,你可能不知道什麼時候就失去焦點了,此時再點擊按鈕執行命令就無效了;有時你又需要還原或設置光標的位置,比如插入圖片後,光標要設置到圖片後面等等之類的。
所以我們需要具有控制光標的能力,具體操作就是在點擊按鈕之前我們可以先存儲當前光標的狀態,執行完命令或者在需要的時候後再還原或設置光標的狀態即可。由於在 chrome 中,失去焦點並不會清除 Seleciton 對象和 Range 對象,所以就像我一開始說的我沒怎麼去了解。。。這裡就只簡要展示兩個方法給大家看下:

富文本原理了解一下?

以上就是今天所要分享的內容,感謝你的閱讀,大讚無疆 。。。。

結語

回到開頭我們講的那個需求,關於圖片旋轉的,根據上面的思路,你可以在蒙層上加個旋轉圖標,並添加點擊事件,然後修改圖片和蒙層 transform 屬性,當然了位置也要變,可能需要些計算,我也沒試過,不知道效果咋樣。

另外一種方法就是在插入圖片之前先對圖片進行處理(比如多一步類似裁剪的功能)再上傳,這樣就可以不用在編輯區裡面處理圖片啦,嘿嘿,目前我就想到這兩種方案了,實際工作中採用的是第二種方式,因為產品的需求不止於旋轉。

最後的最後,不知道大家有沒有更好的方法來對圖片或內容進行處理,歡迎留言探討,See you。


原文鏈接:https://juejin.im/post/5cfe4e8a6fb9a07ec63b09a4#heading-0


分享到:


相關文章: