富文本編輯器的基本原理與實(shí)踐
富文本編輯器,Rich Text Editor, 簡(jiǎn)稱(chēng) RTE, 它提供類(lèi)似于 Microsoft Word 的編輯功能,容易被不會(huì )編寫(xiě)HTML 的用戶(hù)并需要設置各種文本格式的用戶(hù)所喜愛(ài)。它的應用也越來(lái)越廣泛。最先只有 IE瀏覽器支持,其它瀏覽器相繼跟進(jìn),在功能的豐富性來(lái)說(shuō),還是 IE 強些。雖然沒(méi)有一個(gè)統一的標準,但對于最基本的功能,各瀏覽器提供的 API基本一致,從而使編寫(xiě)一個(gè)跨瀏覽器的富文本編輯器成為可能。
在很多開(kāi)發(fā)者看來(lái),富文本編輯器的編寫(xiě)是一件很神秘或者復雜的事情。神秘倒沒(méi)有,復雜的話(huà),確實(shí)如此。但是它的基本原理并不復雜,入門(mén)也不難。今天我們的主題是講述基本原理,并逐步演示一個(gè)簡(jiǎn)單富文本編輯器的產(chǎn)生。這是我在 D2 上的一個(gè)分享內容,在臺上的演講效果不佳,固寫(xiě)下來(lái),希望能夠對感興趣的讀者有所幫助。
富文本編輯器的基本原理
這個(gè)原理實(shí)在是太簡(jiǎn)單了!對于支持富文本編輯的瀏覽器來(lái)說(shuō),其實(shí)就是設置 document 的 designMode 屬性為 on 后,再通過(guò)執行 document.execCommand(‘commandName‘[, UIFlag[, value]]) 即可。commandName 和 value 可以在 MSDN 上和MDC 上找到,它們就是我們創(chuàng )建各種格式的命令,比方說(shuō),我們要加粗字體,執行 document.execCommand(‘bold‘, false)即可。很簡(jiǎn)單是吧?但是值得注意的是,通常是選中了文本后才執行命令,被選中的文本才被格式化。對于未選中的文本進(jìn)行這個(gè)命令,各瀏覽器有不同的處理方式,比方 IE 可能是對位于光標中的標簽內容進(jìn)行格式化,而其它瀏覽器不做任何處理,這超出本文的內容,不細述。同時(shí)需要注意的是,UIFlag 這個(gè)參數設置為 true 表示 display any user interface triggered by the command (if any), 在我們今天的教程中都是 false, 而 value 也只在某些 commandName 中才有,具體參考以上剛給出的兩個(gè)鏈接。
為了不影響當前 document, 通常的做法是在頁(yè)面中嵌入一個(gè) iframe 元素,然后對這個(gè) iframe 內的 document(通過(guò) iframe.contentWindow.document 獲得)進(jìn)行操作。
十分簡(jiǎn)單,是吧?下面我們來(lái)動(dòng)手做一個(gè)。
編寫(xiě)一個(gè)簡(jiǎn)單的富文本編輯器
這個(gè)例子使用了 YUI. 即使你對它不是很熟悉也沒(méi)有關(guān)系,我在這里只使用了它的 DOM 和 Event 的一些跨平臺基本方法。
搭架
在此強調一下很久未曾提及的 unobtrusive. 我們的編輯器是對 textarea 元素的一個(gè)增強(enhencement),就是說(shuō),即使 JavaScript 被禁用了,用戶(hù)還可以通過(guò) textarea 編輯內容。
在這個(gè)例子中,我們將使用 YAHOO.realazy 的命名空間,在之下實(shí)現一個(gè) RTE 的類(lèi)。我們今天的編輯器很簡(jiǎn)單,因此構造器(constructor) 的參數也只有 textarea 一個(gè)。我們使用一個(gè)實(shí)例變量來(lái)保存工具條的各個(gè)項目。實(shí)例初始化放到一個(gè)叫 render 的方法中。這一步的頁(yè)面和代碼見(jiàn)第 1 步。
創(chuàng )建 iframe 并替換 textarea
搭好架子,正如我在前面所說(shuō),建立一個(gè) iframe, 編輯器的所有操作都在 iframe 的 document 內執行。并且把 textarea 隱藏起來(lái)。從第 2 步中可以看到,我們已經(jīng)有了一個(gè) iframe, 但不能輸入任何東西,很正常,我們沒(méi)有打開(kāi)它的 designMode 嘛。
開(kāi)啟 designMode
這一步涉及的東西挺多,也是關(guān)鍵。我們會(huì )創(chuàng )建獲取 iframe 的 document 的方法,并通過(guò)程序的方式向 iframe 寫(xiě)入空頁(yè)而非使用一個(gè)外接的 blank.html. 我們使用一個(gè)類(lèi)屬性 YAHOO.realazy.RTE.htmlContent 來(lái)保存空頁(yè)的 html. 在準備好一切后,就可以開(kāi)啟 designMode 了。頁(yè)面和代碼詳見(jiàn)第 3 步???,我們已經(jīng)可以在 iframe 里輸入東西了。
構建工具條
我們需要操作的工具條!這樣才可以控制 iframe 里的內容,才能稱(chēng)之為編輯器。在此我并不打算實(shí)現太多的功能,只是選擇字形、字號、加粗、斜體、下劃線(xiàn)、居左、居中、居右、超鏈接和插圖作為示例。對于跨平臺,Mozilla Midas Specification 是不錯的參考。ok, 請看第 4 步,我們的工具條出來(lái)了,雖然很丑。我同時(shí)用 CSS 對 iframe 的寬度做出了一些調整。
給工具條加上事件
嗯,工具條出來(lái)了,編輯器看起來(lái)也“人模狗樣”了,你興奮的點(diǎn)啊點(diǎn),沒(méi)什么效果……意料中嘛。我們接著(zhù)給工具條綁定一些事件,讓編輯器內容能夠響應工具條。在這一步,我們把 execCommand 再封一層,前面說(shuō)過(guò),我們用不上 UIFlag,讓它永遠是 false 好了。好,有代碼就有真相,請看第 5 步。如果是正使用 IE, 請先暫時(shí)轉移到其它瀏覽器??吹搅税?,工具條生效了!
解決 IE 的問(wèn)題
well, 如果你沒(méi)有聽(tīng)我的勸告,依然使用 IE,你會(huì )發(fā)現除了字型和字號其它的都不能用。為什么呢?你觀(guān)察一下,有沒(méi)有發(fā)現,其它瀏覽器選擇文本后,再點(diǎn)擊工具條上的項目,被選中的文本是否依然選中的?而 IE 呢,在點(diǎn)擊工具條時(shí),選中的文本馬上失去選中的狀態(tài),所以它們就失敗了。所以,如果我們能夠保證點(diǎn)擊工具條文本保持選中狀態(tài),就可以解決IE 的問(wèn)題了。
Microsoft 給 HTML 標簽一個(gè)很奇怪的屬性 unselectable, 只要設置為 on, 焦點(diǎn)不會(huì )轉移到點(diǎn)擊的元素上,從而保證文本的選中狀態(tài)。
請看第 6 步。這也是解決 IE 頭痛問(wèn)題的關(guān)鍵所在。我曾經(jīng)在這上面費了很大腦筋。
高級主題展望
good, 看看我們現在的代碼,224 行。相比其它動(dòng)輒上萬(wàn)行的編輯器,你可能會(huì )覺(jué)得不可思議。因為我們這個(gè)最基本的編輯器,連 selection 都沒(méi)有用到。很多很酷的效果,比如 Google Doc 里能夠動(dòng)態(tài)改變鏈接文本,使用頁(yè)內層而非彈出的 prompt 來(lái)操作等高級功能,基本上都要用到 TextRange(IE) 或者 Range(W3C). 要命的是這兩個(gè)東西互補兼容,只是相似而已。入門(mén)推薦看PPK 的 Introduction to Range.
在此我們就不深入了,等我有時(shí)間我會(huì )總結一些奇技淫巧(嗚呼,前端開(kāi)發(fā)需要的奇技淫巧太多了,這不是好事情)出來(lái)。

