富文本編輯器(Rich Text Editor)是在網頁上使用的一種所見即所得的文本編輯器,是 Web 應用開發中很常見的需求。
富文本實現
在 HTML 文檔上共有 2 中方式實現富文本編輯器。一種是使用 iframe,另一種是使用 contenteditable 屬性指定 HTML 文檔元素。
iframe
第一種方式是使用 iframe 標簽。
在空白的 HTML 文檔中嵌入一個 iframe,並將 designMode 屬性設置為 "on",文檔就會變成可編輯的,實際編輯的則是 <body> 元素的 HTML,默認值是 "off"。文檔變成可編輯后,就可以像使用文字處理程序一樣編輯文本,通過鍵盤將文本標記為粗體、斜體等等。
作為 iframe 源的是一個非常簡單的空白 HTML 頁面。下面是一個例子:
<!DOCTYPE html> <html> <head> <title>Blank Page for Rich Text Editing</title> </head> <body> </body> </html>
這個頁面會像其他任何頁面一樣加載到 iframe 里。為了可以編輯,必須將文檔的 designMode 屬性設置為 "on"。不過,只有在文檔完全加載之后可以設置。在這個包含頁面內,需要使用 onload 事件處理程序在適當時機設置 designMode,如下面的例子所示:
<iframe name="editor" style="height: 100px; width: 100px"></iframe> <script> window.addEventListener("load", () => { frames["editor"].document.designMode = "on"; }); </script>
以上代碼加載之后,可以在頁面上看到一個類似文本框的區域。這個框的樣式具有網頁默認樣式,不過可以通過 CSS 調整。
contenteditable
第二種方式是使用 contenteditable 屬性指定 HTML 文檔中的元素。該方式是 IE 最早實現的。使用方式是在一個元素上添加 contenteditable 屬性並設置為 true 或者空字符串"",然后該元素會立即被用戶編輯。如下所示:
<div class="editor" id="editorId" contenteditable="true"></div>
元素中包含的任何文本都會自動被編輯, 元素本身類似於<textarea> 元素。通過設置contentEditable 屬性,也可以隨時切換元素的可編輯狀態:
let div = document.getElementById("editorId"); richedit.contentEditable = "true";
contentEditable 屬性有3 個可能的值:"true" 表示開啟,"false" 表示關閉,"inherit" 表示繼承父元素的設置。主流瀏覽器都支持 contentEditable 屬性。
注意:contenteditable 是一個非常多才多藝的屬性。比如,訪問偽 URL data:text/html, <html contenteditable>可以把瀏覽器窗口轉換為一個記事本。這是因為這樣會臨時創建 DOM 樹並將整個文檔變成可編輯區域。
與富文本交互
上面講到了如何在 HTML 文檔中實現富文本編輯器,而現在需要了解要使用什么方式與富文本編輯器交互。document 提供了 execCommand() 方法,該方法會影響使用 contentEditable 屬性實現可編輯區域的元素。方法說明如下所示:
execCommand(commandId: string, showUI?: boolean, value?: string): boolean;
commandId:參數是string值,表示要執行的命令。下面會列出可用命令。showUI:參數是boolean的可選值,表示瀏覽器是否為命令提供用戶界面,一般為false。為兼容的話,該參數需要始終為false,因為 Mozilla 沒有實現,會在其為true時拋出錯誤。value:參數是string的可選值,是一些命令需要額外的參數,默認為null。
該方法執行后,會返回 boolean 值,如果是 false,表示操作不被支持或未被啟用。
注意:在調用一個命令前,不要嘗試使用返回值去校驗瀏覽器的兼容性
不同瀏覽器支持的命令也不一樣。下標列出了最常用的命令。
| 命令 | 作用 | 可選值 |
|---|---|---|
| backColor | 設置文檔背景顏色。在styleWithCss模式下,則只影響容器元素的背景顏色。 | 顏色值<color>字符串(IE使用這個命令設置文本背景色) |
| bold | 切換選中文本的粗體樣式 | null |
| copy | 將選中內容復制到剪貼板 | null |
| createLink | 將選中內容轉換為指向給定URL的鏈接 | URL鏈接值,至少包含一個字符 |
| cut | 將選中內容剪切到剪貼板 | null |
| delete | 刪除選中的內容 | null |
| fontName | 將選中文本改為使用指定字體 | 字體名(例如:"Arial") |
| fontSize | 將選中文本改為指定字體大小 | 提供HTML字體尺寸(1-7) |
| foreColor | 將選中文本改為指定顏色 | 顏色值<color>字符串 |
| formatBlock | 將選中文本包含在指定的HTML標簽中 | 提供HTML標簽,如<h1> |
| indent | 縮進文本 | null |
| insertHorizontalRule | 在光標位置插入<hr>元素 | null |
| insertImage | 在光標位置插入圖片 | 圖片的URL鏈接 |
| insertOrderedList | 在光標位置插入<ol>元素 | null |
| insertunorderedlist | 在光標位置插入<ul>元素 | null |
| insertParagraph | 在光標位置插入<p>元素 | null |
| italic | 切換選中文本的斜體樣式 | null |
| justifyCenter | 在光標位置或者所選內容進行文字居中 | null |
| justifyFull | 在光標位置或者所選內容進行文本對齊 | null |
| justifyLeft | 在光標位置或者所選內容進行左對齊 | null |
| justifyRight | 在光標位置或者所選內容進行右對齊 | null |
| outdent | 減小縮進 | null |
| paste | 在光標位置粘貼剪貼板內容,如果有被選中的內容,會被替換 | null |
| redo | 重做被撤銷的操作 | null |
| removeFormat | 對所選內容去除HTML格式。這是formatBlock的反操作 | null |
| selectAll | 選中編輯區里的全部內容 | null |
| strikeThrough | 切換刪除線 | null |
| subscript | 切換下角標 | null |
| superscript | 切換上角標 | null |
| underline | 切換下划線 | null |
| undo | 撤銷最近執行的命令 | null |
| unlink | 取出所選鏈接<a>的標簽 | null |
| styleWithCSS | 用這個取代useCSS命令。切換使用HTML tags還是CSS來生成標記。 | Boolean值,false使用CSS,true使用HTML |
剪貼板相關的命令與瀏覽器關系密切。雖然這些命令並不都可以通過 document.execCommand() 使用,但相應的鍵盤快捷鍵都是可以用的。
這些命令可以用於修改 iframe 中富文本區域的外觀,如下面的例子所示:
// 在內嵌窗格中切換粗體文本樣式 frames["editor"].document.execCommand("bold", false, null); // 在內嵌窗格中切換斜體文本樣式 frames["editor"].document.execCommand("italic", false, null); // 在內嵌窗格中創建指向www.wrox.com 的鏈接 frames["editor"].document.execCommand("createlink", false, "http://www.wrox.com"); // 在內嵌窗格中為內容添加<h1>標簽 frames["editor"].document.execCommand("formatblock", false, "<h1>");
同樣的方法也可以用於頁面中添加了 contenteditable 屬性的元素,只不過要使用當前窗口而不是內嵌窗格中的 document 對象:
// 切換粗體文本樣式 document.execCommand("bold", false, null); // 切換斜體文本樣式 document.execCommand("italic", false, null); // 創建指向www.wrox.com 的鏈接 document.execCommand("createlink", false, "http://www.wrox.com"); // 為內容添加<h1>標簽 document.execCommand("formatblock", false, "<h1>");
注意:使用bold的命令在瀏覽器中生成的 HTML 差別很大。如在 IE 和 Opera 中使用<strong>標簽,在 Safari 和 Chrome 中使用<b>標簽,在 Firefox 中使用 <span> 標簽。
document 中提供了 queryCommandEnabled() 方法:
queryCommandEnabled(commandId: string): boolean;
commandId:接收string類型參數,是待查詢是否可用的命令。
該方法用於確定對當前選中文本或光標所在位置是否可以執行相關命令。true 指令可用,false 不可用。如下所示:
let result = document.queryCommandEnabled("selectAll");
但要注意,返回 true 並不代表允許執行相關命令,只代表當前選區適合執行相關命令。在 Firefox 中,queryCommandEnabled("cut") 即使默認不允許剪切也會返回 true。
document 還提供 queryCommandState() 方法:
queryCommandState(commandId: string): boolean;
commandId:參數是要確定的命令。
該方法用於確定相關命令是否應用到了當前文本選區。如下所示:
let isBold = document.queryCommandState("selectAll");
富文本編輯器可以利用這個方法更新粗體、斜體等按鈕。
在介紹一下 queryCommandValue() 方法:
queryCommandValue(commandId: string): string;
這個方法用於返回執行命令時使用的值,參考 execCommand() 方法使用的第三個參數。如下所示,如果一段選中文本應用了值為 5 的 "fontsize" 命令,使用該方法會返回 5:
let fontSize = document.queryCommandValue("fontsize");
這個方法可用於確定如何將命令應用於文本選區,從而進一步決定是否需要執行下一個命令。
富文件選區
Selection 對象表示用戶選中的文本范圍或光標的位置,它代表頁面中的文本選區。可以使用 window 或 document 對象調用 getSelection() 方法獲取文本選區。Selection 對象擁有以下屬性。
anchorNode:只讀屬性,描述選區起點所在的節點。anchorOffset:只讀屬性,返回的是選區起點在anchorNode中的位置偏移量 。focusNode:只讀屬性,返回選區終點所在的節點。focusOffset:只讀屬性,返回的是選區終點在focusNode中的位置偏移量。isCollapsed:只讀屬性,返回boolean值,用來表示選區起點和終點是否在同一個位置。rangeCount:只讀屬性,返回選區所包含的DOM范圍數量。
Selection 的屬性並沒有包含很多有用的信息。好在它的以下方法提供了更多信息,並允許操作選區。
addRange(range):把給定的DOM范圍添加到選區。collapse(node, offset):將選區折疊到給定節點中給定的文本偏移處。collapseToEnd():將選區折疊到終點。collapseToStart():將選區折疊到起點。containsNode(node):判斷給定節點是否包含在選區中。deleteFromDocument():從文檔中刪除選區內容。與執行execCommand("delete", false, null)命令結果相同。extend(node, offset):通過將focusNode和focusOffset移動到指定值來擴展選區。getRangeAt(index):返回選區中指定索引處的DOM范圍。removeAllRanges():將所有的區域都從選區中移除。removeRange(range):從選區中移除指定的DOM范圍。selectAllChildren(node):清除選區並選擇給定節點的所有子節點。toString():返回選區中的純文本內容。
下面介紹一個例子,是使用 Selection 對象實現選中文本高亮:
function highlight() { let selection = document.getSelection(); // 取得表示選區的范圍 let range = selection.getRangeAt(0); // 高亮選中的文本 let span = document.createElement("span"); span.style.backgroundColor = "yellow"; // 給選中文本添加背景為黃色的<span>標簽 range.surroundContents(span); }
效果如下:

通過表單提交富文本
因為富文本編輯不是在表單控件中實現,這意味着要將富文本編輯結果提交給服務器,就要手動進行。我們會在表單中添加一個 type="hidden" 的字段,在提交表單時,通過監聽器,從元素中提取出 HTML 並插入隱藏字段中。如下所示:
// form 實例是`<form>` 元素,可以使用DOM獲取 form.addEventListener("submit", (event) => { let target = event.target; target.elements["content"].value = frames["editor"].document.body.innerHTML; });
上述從編輯器中獲取了 HTML 后,將其插入名為 content 的字段中。下面是對於 contenteditable 元素實現的方式:
這里,代碼使用文檔主體的 innerHTML 屬性取得了 iframe 的 HTML,然后將其插入名為 "comments" 的表單字段中。這樣做可以確保在提交表單之前給表單字段賦值。如果使用 submit() 方法手工提交表單,那么要注意在提交前先執行上述操作。對於 contenteditable 元素,執行這一操作的代碼是類似的:
// form 實例是`<form>` 元素,可以使用DOM獲取 form.addEventListener("submit", (event) => { let target = event.target; target.elements["content"].value = document.getElementsByClassName("editor")[0].innerHTML; });
總結
本文介紹了實現富文本編輯器有兩種方式:使用 iframe 和 contenteditable 屬性。介紹了使用 document.execCommand() 方法來實現加粗、斜體樣式等功能,還有一些相應的功能。而且富文本編輯的內容要上傳到服務器,還要將內容先復制到表單中的一個字段上,然后在提交。
