摘要:當然,這只是結(jié)合自己項目的工程結(jié)構(gòu)和特點設置的一套使用方式,僅供參考開發(fā)富文本編輯器的教訓由于項目的時間較緊張,我在頁面上應用了框架的背景下,想當然的想要把也應用于富文本編輯器的開發(fā),事實證明這是不太可行的。
此文已由作者劉詩川授權(quán)網(wǎng)易云社區(qū)發(fā)布。
歡迎訪問網(wǎng)易云社區(qū),了解更多網(wǎng)易技術(shù)產(chǎn)品運營經(jīng)驗。
最近我們的產(chǎn)品有一個需求是要在PC端做一個面向用戶的書評編輯器,讓用戶和編輯在蝸牛讀書上能方便快捷的編輯和產(chǎn)出一些優(yōu)質(zhì)的文章,它的主要難點就是富文本編輯器部分。
這雖然是個業(yè)務需求,但是做業(yè)務的同時也要兼顧技術(shù),所以在跟需求商量好不支持IE8之后,決定采用Vue來作為前端部分的技術(shù)架構(gòu)。
前端架構(gòu)
webpack配置
Vue是一個非常優(yōu)秀的前端MVVM框架,輕量、快速、文檔友好又詳細,代碼組織也非常優(yōu)雅,是我比較偏愛的MVVM架構(gòu)。Vue官方提供了非常方便快速上手的腳手架Vue-cli,但是由于跟我們這邊使用的Java Web架構(gòu)有一些不太適合的地方,所以我并沒有使用它,不過我也是對Vue-cli做了一番詳細的學習后來搭建自己的webpack配置。
下面是我的生產(chǎn)環(huán)境的部分webpack配置,其實并不復雜,因為我的業(yè)務場景也并不復雜,現(xiàn)在的各種插件功能也足夠強大。
webpack.prod.config.js
devtool: "source-map", plugins: [ new CleanWebpackPlugin(["dist"]), new ExtractTextPlugin("[name].css"), new webpack.DefinePlugin({ "process.env": { NODE_ENV: ""production"" } }), new webpack.optimize.CommonsChunkPlugin({ name: "vendor", minChunks: function(module, count) { return ( module.resource && /.js$/.test(module.resource) && module.resource.indexOf("node_modules") >= 0 ) } }), new webpack.optimize.CommonsChunkPlugin({ name: "manifest", filename: "manifest.js", chunks: ["vendor"] }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), ]
主要就是借鑒了Vue-cli中的code split思路,開發(fā)環(huán)境的webpack配置區(qū)別不大,只是sourcmap設置改為了devtool: "#cheap-module-eval-source-map",去掉了代碼壓縮等。
需要注意的一點是,我在生成環(huán)境下的webpack配置中使用了vue-loader附帶的postcss預處理器中的cssnano插件進行css部分的代碼壓縮,但是這個插件打包時會將z-index:10壓縮成z-index:1,需要添加設置zindex: false才能避免這個問題,而且cssnano插件默認還有一個特性就是會刪除沒有使用到的css部分,比如我們?yōu)镃SS3動畫所需構(gòu)建的keyframes,居然也會被cssnano認為是沒有被使用的css,壓縮過程中也刪掉了,這個就有點費解了,所以為了避免這種情況,我們需要增加設置discardUnused: false:
webpack.prod.config.js
rules: [{ test: /.vue$/, loader: "vue-loader", options: { loaders: { css: ExtractTextPlugin.extract({ use: "css-loader", fallback: "vue-style-loader" }), scss: ExtractTextPlugin.extract({ use: ["css-loader","sass-loader"], fallback: "vue-style-loader" }) }, postcss: [ require("autoprefixer")({ browsers: ["> 1%"] }), require("cssnano")({ zindex: false, discardUnused: false }) ], } }]
與Java Web的結(jié)合
為了將css文件抽離出來,我在開發(fā)環(huán)境也沒有使用Hot Module Reload機制(使用了ExtractTextPlugin抽離css文件后,修改css樣式不能通過HMR自動更新,需手動刷新)。
我們部門這邊的Java Web除了一些簡單的靜態(tài)活動頁,主要頁面的承載頁都會配置在另外的一個存放freeMarker的ftl文件的文件夾中,有別于靜態(tài)文件的存放位置,這是部門中的Java Web一直沿用的文件結(jié)構(gòu),不好也沒太大必要去改變它。
這就使得Vue-cli或者一些常見的webpack配置中的根據(jù)文件hash生成打包文件再使用html-webpack-plugin自動注入承載頁的功能不太好實現(xiàn),所以就需要結(jié)合部門自己的情況定制比較符合自己項目的打包流程。
我們有個網(wǎng)站應用自動部署平臺,它的功能除了解析和編譯后端工程代碼,還會自動分析頁面引用的靜態(tài)資源,然后將資源的URL替換為對應的CDN域名的下的資源鏈接并添加資源MD5值相關(guān)的查詢值后綴,比如/static/js/app.js會在自動部署后變成//yuedust.yuedu.126.net/snail_st/static/js/app.js?a63ed8a8。
所以既然目前項目中已經(jīng)有了CDN域名替換和文件hash計算的功能,我在webpack打包中就沒必要再多此一舉了,而且,我還可以利用這一特性,固定的設置承載頁引用的靜態(tài)資源的URL,部分代碼如下:
index.ftl
當然,這只是結(jié)合自己項目的Java Web工程結(jié)構(gòu)和特點設置的一套webpack使用方式,僅供參考
開發(fā)富文本編輯器的教訓
由于項目的時間較緊張,我在頁面上應用了Vue框架的背景下,想當然的想要把Vue也應用于富文本編輯器的開發(fā),事實證明這是不太可行的。
富文本中的數(shù)據(jù)渲染
Vue是數(shù)據(jù)和展現(xiàn)雙向綁定的,這使得特定格式的數(shù)據(jù)渲染成對應的html非常的方便。
但是網(wǎng)頁上的富文本編輯器普遍都是利用的是元素的contenteditable屬性,這個屬性是無法實現(xiàn)雙向綁定的,要想實時保存富文本數(shù)據(jù),只能監(jiān)控元素的輸入事件,然后讀取元素的innerText后再去修改數(shù)據(jù),但是一旦修改了數(shù)據(jù),就會觸發(fā)Vue的視圖更新,導致你編輯元素的innerText被重新渲染,元素一旦被重新渲染,用戶輸入時的獲取的光標焦點就消失了,而且在windows和mac os下的輸入法實現(xiàn)有些不一樣,mac下的輸入法輸入中文會先將用戶輸入的拼寫填充到輸入元素中,導致獲取的innerText不準確,所以想要利用Vue的數(shù)據(jù)雙向綁定機制來開發(fā)富文本部分,又想要實現(xiàn)數(shù)據(jù)的實時保存,存在很多問題。
富文本中的不可編輯區(qū)域
我們的書評內(nèi)容的數(shù)據(jù)結(jié)構(gòu)是一個各種item類型組成數(shù)組,item的類型有:文字、圖片、書籍和筆記,富文本編輯器需要將這些數(shù)據(jù)展現(xiàn)出來并且可編輯,其中書籍和筆記的數(shù)據(jù)結(jié)構(gòu)只能添加或者刪除,而不能修改,這就與傳統(tǒng)的富文本編輯器存在一定的區(qū)別,即富文本編輯器區(qū)域需要插入或者刪除不能修改的元素。這個需求使得一個普通的富文本編輯器變得特殊起來,一開始我的思路是在contenteditable="true"的編輯器主體內(nèi)插入contenteditable="false"的dom結(jié)構(gòu),這導致插入部分的文本無法與編輯器很好的交互,包括刪除、撤銷、選中等,最后找到了另外一種比較理想的解決辦法。
開發(fā)富文本編輯器的一些經(jīng)驗
以下是我在開發(fā)一個本業(yè)務場景下的富文本編輯器的一些經(jīng)驗:
在開源富文本編輯器的基礎上開發(fā)
知乎上有個問題,叫做為什么都說富文本編輯器是天坑?,里面提到的很多開發(fā)富文本編輯器會遇到的一些難點,而我的第一版也是想著自己從頭開始開發(fā),但是的確碰到了很多沒想到的問題,修修補補最終結(jié)果還是不滿意。
所以如果是需要一個常規(guī)功能的富文本編輯器,盡量選擇成熟穩(wěn)定的開源項目,保證穩(wěn)定可靠,如果需要像我一樣開發(fā)一個符合特定業(yè)務場景的富文本編輯器,也盡量在開源項目的基礎上進行二次開發(fā),這樣雖然會有一些代碼冗余,但是能幫助你避開許多前人已經(jīng)踩過的坑,而且也能從閱讀這些項目的源碼中學習到不少忽視的知識和特性。
我選擇的是國內(nèi)的一個個人開發(fā)者維護的叫做wangEditor的項目,它比較輕量,源碼也比較清晰便于二次開發(fā)。
基于DOM的數(shù)據(jù)渲染
要想在WEB端實現(xiàn)富文本編輯,經(jīng)過我踩的一些坑,我覺得最終還是要回歸于DOM的,Vue或者其他MVVM框架確實給開發(fā)和維護帶來很大的遍歷,但是在富文本編輯這塊,還是沒有DOM API來的可控。我的方案是根據(jù)服務端提供的一篇書評的items,組織出相應的HTML,然后再交給富文本編輯器進行初始化。
基于瀏覽器的document.execCommand API進行開發(fā)
當一個HTML文檔處于設計模式(designMode)或者一個HTML元素設置了contentEditable="true"時,我們可以使用execCommand方法,運行一些命令來操縱可編輯區(qū)域的內(nèi)容,這個API可以快速可靠的對富文本區(qū)域的選區(qū)內(nèi)容進行一系列的操作,最關(guān)鍵是,支持撤銷和重做功能,并且在撤銷和重做的過程中能夠完美的保持選區(qū)的狀態(tài),這一點非常重要,我們可以通過保存html來實現(xiàn)內(nèi)容的撤銷和重做,但是選區(qū)或者說光標的撤銷和重做,用Javascript很難完美的控制,如果只是保存之前選區(qū)的range對象,是不能復原選區(qū)或者光標的。
具體支持的API可以參考MDN的文檔。
即使對于一些文檔中不支持的API,也建議通過以上API來組合實現(xiàn),比如一段HTML內(nèi)容的替換,應該先通過Javascript建立相應的選區(qū),然后運行delete命令刪除該段內(nèi)容,再通過insertHTML來插入所需的HTML,這樣才能充分的利用瀏覽器的撤銷和重做功能,并且與其他的操作串聯(lián)起來。
富文本中的換行
富文本編輯器中的換行是一個值得注意的問題,我在開發(fā)書評編輯器的時候,遇到了一些問題:
富文本中展示換行看起來很容易,有幾個方案,比如設置CSS的white-space再配合換行符,或者在DOM中添加
元素,看起來都能達到目的。但是書評編輯器特殊的地方在于,這是一個已經(jīng)制定好了數(shù)據(jù)結(jié)構(gòu)并且在客戶端上也有編輯器,這就涉及到Web、iOS、Andorid三個端的一致性問題。
因為在客戶端上是沒有
概念的,客戶端編輯器上需要換行位置插入的都是回車符,也就是n,而這些換行符在WEB上如果需要顯示成換行,就需要設置white-space為pre或者pre-line
如果設置為white-space: pre;,確實可以原樣顯示文本換行,但是如果是這樣一條數(shù)據(jù):
這是書評中的一條文本數(shù)據(jù),其中有兩個換行符,代表要展示成三行,其中有一個空行,實際需要展示的效果是下圖這樣的:
這樣的數(shù)據(jù)如果要展示在一個DOM節(jié)點中,設置為white-space: pre;,換行雖然保留了,但是由于第一行數(shù)據(jù)是連續(xù)的,white-space: pre;原樣保持了數(shù)據(jù)的換行,導致了第一行超出了DOM的最大寬度,這樣的方式顯然就行不通了。
如果設置成white-space: pre-line,pre-line可以在正確顯示換行符的同時讓超出一行的文字自動換到下一行,看起來很完美。但是,一旦在換行符之后(比如中間空的那行)輸入文字,問題又出現(xiàn)了,在white-space: pre-line的元素中,如果在換行符之后輸入文字,換行符會被刪除,文字將會跳動到上一行繼續(xù)顯示,這樣顯然是不行的。
最終的方案只有剩插入
元素來實現(xiàn)換行了,通過
實現(xiàn)的換行,不會出現(xiàn)輸入文字換行失效的問題,也不需要父元素設置white-space: pre;,所以我們需要將客戶端在文本中插入的n轉(zhuǎn)換成
,最后把HTML結(jié)構(gòu)重新解析成書評數(shù)據(jù)的時候,又需要將它們轉(zhuǎn)換回來以便保證客戶端編輯和展示的一致性,當然這中間還有一系列的轉(zhuǎn)換邏輯,包括針對客戶端老版本的編輯器的一些BUG做的兼容,最后為了實現(xiàn)一致還是廢了一番功夫的。
富文本中的不可編輯區(qū)域
如上面兩圖,我們的書評中有一部分內(nèi)容是用戶引用的某一本書籍、或是用戶在閱讀時記錄的書籍原文,這些數(shù)據(jù)結(jié)構(gòu)都是不能被修改的,只能插入或者刪除,一開始我的思路是把該部分DOM結(jié)構(gòu)設置為contenteditable="false",但是這樣的設置代碼上不管怎么去彌補體驗上都不夠好。
后來我轉(zhuǎn)變了思路,既然這就是一段不可編輯只能觀看的DOM,而富文本編輯器里插入的圖片是能夠很好的與文字一起被很好的操作和維護的,那么為什么不把不可編輯的展示區(qū)域直接轉(zhuǎn)換為圖片插入到富文本區(qū)域呢,事實證明這個思路最后的體驗非常好,除了一個小的技術(shù)問題,下面一點會說明。
將DOM轉(zhuǎn)換為圖片
要將一個DOM轉(zhuǎn)化為圖片,社區(qū)里已經(jīng)有不少很成熟的開源庫可以使用,比如我使用的是dom-to-image,需要注意的就是一個問題:DOM轉(zhuǎn)化為圖片,基本都利用到了canvas的toDataUrl()功能將圖片轉(zhuǎn)化轉(zhuǎn)化為base64編碼的URL,這里面有一個安全策略,就是如果canvas中繪制的DOM結(jié)構(gòu)中有圖片,而該圖片與當前頁面的域名不一樣(這在我們的開發(fā)場景中很常見),出于安全策略的限制,此時瀏覽器是不允許調(diào)用canvas的toDataUrl()方法的,而我們的書籍卡片中必定會有書籍的封面,該封面的域名是我們的CDN域名,所以轉(zhuǎn)換成圖片被限制了。
要想解決這個辦法,就涉及到一個前端的IMG標簽的屬性:crossOrigin,如果將這個屬性設置為anonymous,瀏覽器就會為這張圖片的請求的Request Headers 中附帶Origin為當前域名的這一行信息,告訴圖片所在的靜態(tài)資源服務器,這張圖片我需要跨域訪問以及我的域名,請在圖片的Response Headers中附加Access-Control-Allow-Methods和Access-Control-Allow-Origin這兩行信息,如下圖:
這樣請求得到的圖片渲染到canvas中,瀏覽器才不會限制該canvas轉(zhuǎn)化為base64的URL。
這一特性需要服務端的支持,有的服務端就算附加了這個Request Headers字段依然不會返回想要的Response。
但是在支持這一特性的服務端,有時候設置了crossOrigin="anonymous"依然顯示這個錯誤,不是這個屬性沒生效,而是我們的圖片一般是存放在CDN上的,而CDN為了更快的返回用戶的請求,會把圖片的響應緩存下來,而這些緩存下來的響應顯然是沒有Access-Control-Allow-Methods和Access-Control-Allow-Origin這兩行信息的,所以這時候即使我們認為自己的請求包含了crossOrigin="anonymous",CDN服務器不認為這是一個不同的請求,所以返回給我們的響應是之前就緩存好的,導致了這個問題的發(fā)生。
這種情況就需要我們?yōu)槲覀冋埱蟮膱D片URL后添加一個時間戳來避免CDN服務器的緩存。
避免使用CDN來提高渲染速度
前端開發(fā)中說到提高頁面的加載速度,一般都會提到最大限度的利用CDN緩存靜態(tài)資源,以提高靜態(tài)資源的訪問速度,從而更快的將網(wǎng)頁內(nèi)容呈現(xiàn)給用戶。
但是,我上面提到的將含有跨域CDN圖片的DOM節(jié)點渲染成圖片的情況下,向CDN代理節(jié)點請求圖片資源反而會比我們直接向靜態(tài)資源源站點請求要來的慢,其實這也很好理解:
為了將含有跨域CDN圖片的DOM利用HTML5``canvasAPI渲染成圖片,我們就需要為該圖片的添加crossOrigin="anonymous"屬性,并且為圖片的請求URL添加一個時間戳
如果我們訪問的是CDN域名下的圖片,同時又為URL添加了一個全新的時間戳,那么這個圖片資源的請求對于CDN代理節(jié)點來說肯定是全新的,也就是會認為本節(jié)點上沒有這個資源的緩存
CDN代理節(jié)點遇到一個自己沒有緩存的資源,它就會向靜態(tài)資源的源站點去請求,得到結(jié)果后再轉(zhuǎn)發(fā)給用戶,這等于說我們這個帶有時間戳的圖片URL的請求,不但沒能利用的CDN的緩存提速,反而由CDN代理節(jié)點充當了一次中介,這顯然會增加資源的返回耗時
上面兩圖分別就是請求CDN域名圖片的耗時和請求源站點圖片的耗時,經(jīng)過多次測試,可以發(fā)現(xiàn)請求CDN域名圖片的耗時基本在200ms以上,而向源站點的請求基本都在100ms以下,所以,有的時候,比如這種特殊情況下,請求CDN域名下的資源可能反而會增加請求的耗時。
Promise大法好
根據(jù)上面提到的流程,需要我把從服務端拿到的一個包含各種類型item的數(shù)組解析成一個HTML字符串,其中包含了書籍和筆記類型的item需要轉(zhuǎn)化成的base64格式的圖片,這就出現(xiàn)了時序上的問題:
文本和圖片類型的item,可以直接得到對應的HTML字符串,而書籍和筆記類型的item,則需要通過網(wǎng)絡請求和canvas轉(zhuǎn)換,但是最終我又需要得到整個的初始HTML內(nèi)容來初始化富文本編輯器,然后再讓用戶可以去在這些HTML DOM節(jié)點上進行編輯,這就需要用到Promise.all這個API了,代碼示例如下:
App.vue
/* 將服務端返回的書評items轉(zhuǎn)換為html string傳輸給富文本編輯器 @param {json array} items 書評items @return {promise} 所有items處理好后返回resolve(htmlStr), 否則reject(error) */ convertItemsToHtml(items){ return new Promise ( (resolve, reject) => { let htmlStr = ""; let itemStr = ""; let itemPromises = items.map( item => { return new Promise( (resolve, reject) => { switch(item.resourceType){ case "Text": itemStr =
"Text">${item.text}
; resolve(itemStr); break; ... case "BookNote": let $BookNoteEle = $("BookNote">"BookNote" >"${escape(JSON.stringify(item))}" src="${dataUrl}">
以上就是我在開發(fā)特定業(yè)務需求的富文本編輯器中遇到的一些問題和總結(jié)的一些經(jīng)驗,可能會有一些錯誤,希望幫忙指正。 其他一些常見的富文本編輯中會遇到的問題,可以通過學習一些開源的成熟富文本編輯器項目來得到解答。
免費領取驗證碼、內(nèi)容安全、短信發(fā)送、直播點播體驗包及云服務器等套餐
更多網(wǎng)易技術(shù)、產(chǎn)品、運營經(jīng)驗分享請點擊。
文章來源: 網(wǎng)易云社區(qū)
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/25314.html
摘要:精讀前端可以從多個角度理解,比如規(guī)范框架語言社區(qū)場景以及整條研發(fā)鏈路。同是前端未來展望,不同的文章側(cè)重的格局不同,兩個標題相同的文章內(nèi)容可能大相徑庭。作為使用者,現(xiàn)在和未來的主流可能都是微軟系,畢竟微軟在操作系統(tǒng)方面人才儲備和經(jīng)驗積累很多。 1. 引言 前端展望的文章越來越不好寫了,隨著前端發(fā)展的深入,需要擁有非常寬廣的視野與格局才能看清前端的未來。 筆者根據(jù)自身經(jīng)驗,結(jié)合下面幾篇文章...
摘要:畢老師運營這明顯是廣告,但為什么要在這里發(fā)過去一段時間,由于團隊中的各種高富帥人生淫家都在全世界旅游,我們的開發(fā)進度比較慢當然之前也一直很慢,因為想要做一個慢產(chǎn)品。 Gradchef · 畢老師運營 這明顯是廣告,但為什么要在這里發(fā)? 過去一段時間,由于團隊中的各種高富帥、人生淫家都在全世界旅游,我們的開發(fā)進度比較慢(當然之前也一直很慢,因為想要做一個慢產(chǎn)品)。但是,我們一直都在堅...
摘要:目前的技術(shù)棧主要的采用由于是個人項目,所以數(shù)據(jù)請求都是用了代替。后續(xù)會出一系列的教程配套文章,如如何從零構(gòu)建后臺項目框架,如何做完整的用戶系統(tǒng)如權(quán)限驗證,二次登錄等,如何二次開發(fā)組件如富文本,如何整合七牛等等文章,各種后臺開發(fā)經(jīng)驗等等。 完整項目地址:vue-element-admin系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎篇)系類文章二:手摸手,帶你用vue擼后臺 系列二...
摘要:但能拷貝圖粘貼后不失真通常是收費富文本編輯器才具備的能力。是否支持編程語言高亮,例如按,語言高亮是否支持數(shù)學公式等等因此選擇了兩款富文本編輯器,支持截屏粘貼,當做跟蹤系統(tǒng)時這個功能特別有用。 一、Web應用技術(shù)棧 在開發(fā)Web應用時,通常會使用到以下技術(shù)棧: showImg(https://segmentfault.com/img/bVbwceG);對應這些技術(shù)棧都已有相應的開源產(chǎn)品...
摘要:社區(qū)的認可目前已經(jīng)是相關(guān)最多的開源項目了,體現(xiàn)出了社區(qū)對其的認可。監(jiān)聽事件手動維護列表這樣我們就簡單的完成了拖拽排序。 完整項目地址:vue-element-admin 系類文章一:手摸手,帶你用vue擼后臺 系列一(基礎篇)系類文章二:手摸手,帶你用vue擼后臺 系列二(登錄權(quán)限篇)系類文章三:手摸手,帶你用vue擼后臺 系列三(實戰(zhàn)篇)系類文章四:手摸手,帶你用vue擼后臺 系列...
閱讀 622·2021-11-18 13:12
閱讀 1344·2021-11-15 11:39
閱讀 2506·2021-09-23 11:22
閱讀 6247·2021-09-22 15:15
閱讀 3690·2021-09-02 09:54
閱讀 2344·2019-08-30 11:10
閱讀 3274·2019-08-29 14:13
閱讀 2932·2019-08-29 12:49