富文本编辑器初探
長期以來,作為用戶我是富文本編輯器的使用者,作為前端開發,我也只是富文本插件的使用者,對內部實現細節不甚了解,使用上也只停留在調用插件提供的API,實現一些業務邏輯。最近的項目,需要開發一個簡易富文本編輯器,也算是讓我有機會對其一窺究竟。
可編輯富文本的方式
我們知道form表單中的input、textarea之類標簽是支持內容可編輯的,但并不支持富文本,如果在這些標簽里粘貼帶格式的內容,會被去格式,只保留文本內容。如果想設置可編輯富文本,有兩種方式:
- 嵌入空頁面的iframe,并設置designMode屬性值為“on”,這樣整個文檔就變得可以編輯。
需要在嵌入頁面加載之后,動態設置iframe文檔的designMode屬性。
- 使用contenteditable屬性
該屬性最早是由IE實現,且可以作用于頁面中的任何標簽,只需要在文檔里給標簽設置以上屬性即可,無需嵌入iframe、設置js屬性,所以這種方式也是目前富文本編輯器插件中更多采用的方式;
<div class="editbox" id="richtext" contenteditable><p></p><p contenteditable="false"></p> </> 復制代碼這樣,此div元素中包含的內容就可以編輯了,當然也可以設置子元素(如第二個P元素)為不可編輯。通過js設置元素的該屬性,也可以改變編輯模式:
var elm = document.getElementById('richtext'); elm.contentEditable = 'true';復制代碼contenteditable屬性有三個可能的值:'true'表示打開編輯模式,'false'表示關閉,'inherit'表示從父元素繼承此屬性值。contenteditable屬性兼容性較好,在主流瀏覽器包括IE以及目前大部分的移動端瀏覽器上,都得到支持。
操作富文本
常見的富文本編輯器插件,如wangEditor、百度的UEditor,都有各種豐富的菜單區域來設置編輯內容及格式,如常規的設置標題、文字加粗、超鏈接等,更勝者插入圖片、視頻及自定義的內容結構等,而實現這些功能的API就是document.execCommand(),這個方法是與富文本編輯器進行交互的主要方式。
語法:
bool = document.execCommand(aCommandName, aShowDefaultUI, aValueArgument) 復制代碼- 返回值:布爾型,false表示操作不支持或未被啟用
- aCommandName,命令名稱,如“bold”
- aShowDefaultUI,是否為該命令提供用戶界面,一般設為false,主流瀏覽器沒實現該功能
- aValueArgument,某些命令的額外參數(insertImage命令需要提供插入的圖片的url)
所有支持的commands,可查閱MDN;其中,與剪貼板相關的命令(copy、cut、paste)各瀏覽器實現差異較大,使用時需關注瀏覽器差異。
常用的命令舉例:
// 加粗 document.execCommand('bold', false, null); // 超鏈接 document.execCommand('createlink', false, 'https://www.kaola.com'); // 格式化為h1標題 document.execCommand('formatblock', false, '<h1>');復制代碼【注意】雖然所有瀏覽器都支持以上命令,但這些命令生成的html結構仍有差別。如bold命令,IE和Opera會使用<strong>標簽包裹文本,而Safari和Chrome則使用<b>標簽,firefox使用<span>。
與命令相關的方法:
- queryCommandEnabled 返回布爾值,用于檢測是否可以針對當前選擇的文本或當前光標位置執行某個命令;
- queryCommandState 返回布爾值,用于判斷當前選擇的文本是否已經應用了指定的命令;
可以使用這個方法,來設置編輯器中加粗、斜體等按鈕的狀態。
- queryCommandValue 用于獲取執行某個命令時,傳入的值(即execCommand()方法的第三方參數)
富文本選區 Seletion
Seletion對象是指用戶選中的文本范圍或鼠標的當前位置,通過window.getSelection()來獲取該對象。
Seletion對象的屬性如下:
- anchorNode:選區起點所在節點;
- anchorOffset:anchorNode中包含在選區內的字符數;
- focusNode:選區終點所在節點;
- focusOffset:focusNode中包含在選區內的字符數;
- isCollapsed:boolean,選區的起點與終點是否重合,如果是,可以認為當前沒有內容選中;
- rangeCount:選區中包含的DOM范圍的數量;
- type:描述當前選區的類型
Selection對象的方法參閱MDN。這些方法在富文本編輯器插件里都是很有用的方法,比如控制光標的方法collapse()、collapseToEnd()、collapseToStart(),可以設置插入內容之后光標的位置; 獲取選區包含的文本的方法toString();getRangeAt(index)方法返回索引對應的選區中的DOM范圍,即range對象
來看一個例子:
// 獲取選區內容的位置 function getSelPos () {let sel = window.getSelection()let rg = sel.getRangeAt(0)let elmRect = rg.getClientRects()[0]let editorRect = $('.j-editor')[0].getBoundingClientRect() // 編輯器容器let pos = {}if (elmRect) {// 選區內容居中位置距容器的左距離pos.x = elmRect.left - editorRect.left + elmRect.width / 2 pos.y = elmRect.top - editorRect.top}return pos }復制代碼上述方法,可以獲取當前選區相對于編輯器容器的位置,可以用來設置在選區附近出現的工具條等。 想實時監測選區的變化,可以監聽onselectionchange事件
// 高頻事件,做好節流 document.onselectionchange = _.debounce(this.onSelect, 100) 復制代碼處理paste內容
如果往富文本編輯器里粘貼內容,是會把內容的樣式也粘貼進來的,瀏覽器自動會把應用到某個標簽的樣式內聯到此標簽的style屬性。但更多的時候我們只是需要保留里面的部分格式,需要針對剪貼板中的內容進行過濾、格式化以及特定內容保留等。
editorElem.on('paste', event => {event.preventDefault();let clipboardData = event.clipboardData || event.originalEvent && event.originalEvent.clipboardData || {};let text = clipboardData.getData('text/plain');let html = clipboardData.getData('text/html');})復制代碼通過偵聽paste事件,能獲取到事件對象上的clipboardData對象,獲取粘貼的內容,可以通過getData方法獲取剪切版上的純文本或html結構。 有了html結構,就可以轉成dom對象,針對處理了。默認情況下,剪貼板中的
下面重點說下對剪貼板中圖片的處理。如果剪貼板中的網頁元素包含圖片,即img標簽,如果直接粘貼到編輯器,該圖片的鏈接地址是原網頁所在的圖片地址,這里就要考慮到,如果外鏈別人網站的圖片,就有可能有朝一日這個圖片不可用,所以還是要放到自家的服務器上才放心。這里就涉及到,已知一張圖片的可訪問的鏈接地址,如何把該圖片上傳到自己的服務器上呢?
不考慮兼容性,給出一種可行的方案:通過canvas畫布獲取圖片的數據,并將數據轉為blob對象并進行上傳。步驟如下:
以上是將在線的圖片上傳到服務器的一種解決方法,也在項目中進行了實踐。對于剪貼板內存中的圖片內容,可以通過getAsFile()方法來獲取進而上傳:
// 處理內存中的圖片 if (clipboardData.items[0]) {let item = clipboardData.items[0]let type = item.type;let regResult = type.match(/image\/(.+)/)if (regResult) {let blob = item.getAsFile();// 調用編輯器的通用上傳接口editor.uploadImg.uploadImg([blob])} }復制代碼最后
以上,只算上對富文本編輯器的基本知識點進行了初步的梳理,如果想自己造輪子,擼一個編輯器出來,需要解決的問題還有很多,可以看下知乎上的討論為什么都說富文本編輯器是天坑?,里面提到實現一個令人滿意的編輯器需要各種填坑,以及良好的設計模式,路漫漫其修遠兮……
參考文獻
- JavaScript標準Selection操作
- html元素contenteditable屬性如何定位光標和設置光標
- 關于 HTML5 的文件上傳處理,兼容,以及 BLOB 對象的使用
- 《JavasSript高級程序設計》 - 富文本編輯
- 文件和二進制數據的操作
by lzf
盡量關注網易考拉前端團隊微信公眾號
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
- 上一篇: 《数字图像处理(第三版)》 第三章 数字
- 下一篇: iSCSI 2-环境搭建一