本文做者:ziven27前端
原創聲明:本文爲閱文前端團隊 YFE 成員出品,請尊重原創,轉載請聯繫公衆號 ( id: yuewen_YFE ) 獲取受權,並註明做者、出處和連接。web
Inkstone 是一個面向海外原創做家和翻譯的創做平臺。大量的做家和做品,意味着大量的書封製做需求。這以前是須要設計師投入精力,或者花錢購買來解決的,淘寶的 5 元書封,說便宜也不便宜,和用戶本身上傳的書封同樣,不保證版權方面沒有問題,存在着各類風險。而此時瀏覽器端書封製做系統成爲了咱們解決這個需求的一大利器。做家只須要登陸咱們 Inkstone「 海外做家創做平臺 」,短短几步就能製做出一個精美的有版權書封。canvas
海外書封系統主要分爲純色背景和圖片背景兩條線。純色背景須要四步,圖片背景須要五步就能夠完成咱們的書封製做。瀏覽器
這一切也早已在去年國內起點做家助手,張鑫旭老師帶領下實現了中文版的書封製做系統。而咱們海外測這邊思路和國內是同樣的,可是仍是有不少非中文體系下的難點須要作額外的努力。緩存
對於中文版書封系統,張鑫旭老師在他的博客上已經有不少相關內容給你們介紹。本文着重會在區別於國內書封的幾個點地方給你們介紹:bash
最開始拿到這個需求,第一反應就是將不一樣的圖層對應成咱們 DOM 的分層,而後直接將處理好的 DOM 導出爲圖片便可。雖然用 DOM 操做雖然對文本的處理會比 Canvas 優秀不少,可是在圖像處理這一塊仍是 Canvas 可能性更高。好比要實現一個文字須要同時疊加圖片紋理和背景顏色的這件事,基於 DOM 目前我好像也沒有找到合理的解決方案。服務器
因此選擇瞭如上圖所示的層級模式。背景層就是 Canvas 疊加濾鏡以後的 Base64 圖片,中間層是用 Canvas 直接繪製的文字「 書名 + 做者名 」,最上面一層是 LOGO 和水印的圖片。 當用戶點擊上傳按鈕的時候,再用 Canvas 將這個三個層級疊加成咱們想要的書封,提交到咱們的服務器。網絡
對於圖片濾鏡處理的相關技術方案你們能夠去張鑫旭老師博客上搜索關鍵詞:濾鏡。這邊會着重在濾鏡緩存優化機制上給你們介紹。ide
爲了體現優質的濾鏡效果,和國內書封同樣選擇的是電影級別的 3D LUT 濾鏡。但是這種級別的濾鏡,大部分文件大小都接近 1M,若是在用戶每次點擊按鈕以後,纔去加載對應文件再渲染,這整個等待時間是很長的。而且更讓我吃驚的是,渲染濾鏡是一個可能比加載還要花時間的過程。wordpress
海外前端須要生成的書封的尺寸是 8101080,而這也就對應着 874, 800 個像素點,而這種 3D LUT 濾鏡幾乎是須要去處理每個像素點,可想而知這整個計算量是很是大的。再看看咱們在預覽狀態下的書封尺寸實際上是隻有 240320 的,用戶在點擊切換濾鏡的時候,並不表示他必定會用這張圖,有可能真的只是看看。
單位: ms
因此這邊的處理方案是在預覽狀態下,只會去處理 480*640「 2倍圖 」這個尺寸的書封。從上圖能夠看到咱們總體的平均加載時間,減小了近 63% 的時間。
當用戶真正點擊上傳的時候,纔去對原始尺寸的圖片作濾鏡處理。而此時,由於濾鏡文件已經加載過了,至關於只須要濾鏡渲染的時間,一箭雙鵰。
而後咱們再來看看預加載邏輯,當用戶進入頁面的時候默認是沒有應用濾鏡的。在這個時候會預先加載第一個濾鏡,加載完成渲染,而後加載第二個濾鏡,加載完成渲染。渲染好的圖片會轉成 Base64 的 URL,放到右側 IMG 列表裏「 這個列表在生產環境會隱藏 」。當用戶點擊切換濾鏡按鈕的以後,只須要從右側列表中拿出對應序號的 URL,替換左側的展現區域中的背景圖片的 URL 便可。
這樣作至關於咱們老是先於用戶兩步,去嘗試處理圖片的濾鏡效果。最大限度的減小了用戶等待的時間。甚至當用戶走完一圈以後,切換濾鏡就等於交換兩張 Base64 圖片的 URL,沒有加載,也沒有濾鏡處理,完美。
國內對於中文字體的處理咱們用自建字體服務 Y-font 「 閱文中文字體接口服務 」支撐,能夠按需加載書封用到的幾個文字的字體。可是海外和國內是不共享服務器的,致使於咱們直接無法使用這套服務。不過好在和中文字體比起來英文字體自己就比較小,再加上 Google Font
的加持,即便全量加載也不會有太大的問題。
海外書封的字體列表自己還和用戶選擇的做品分類有關係。一開始我是給每一個分類都準備了一個存放字體列表的 CSS 文件,當用戶切換做品分類的時候,切換對應的 CSS 文件。然然後面發現這個是我想多了。
對於字體瀏覽器是有本身的處理邏輯了。簡單的說就是,頁面中若是沒有任何一個文字設置了對應的字體,即便你引入了字體的 @font-face
這個字體也不會被加載。當你給某個文字設定了font-family
以後這個字體文件纔會加載。當這個字體文件加載成功以後,這個設置了對應font-family
的文字會從新進行繪製。
因此最後我將不一樣分類的字體文件都合併成了一個,由於不一樣分類下的字體是有重疊的,反而讓總體的字體 CSS 文件變小了。後續經過給文字設置 font-family
來控制字體的加載。
如圖所示,我會在頁面中直接輸出用戶選擇分類下的字體列表,並只給前五個的文字設置font-family
也就是說我預加載了這五個字體。由於用戶選擇分類是在第一步,切換字體是在第最後一步,因此當用戶到達第五步的時候,這五個字體可能早就已經預加載好了。
當用戶切換字體的時候,我會從用戶選擇的字體序號開始,給後五個文字設置font-family
,至關於用戶真正切換字體的時候只加載一個字體「有四個已經預加載好了」。而後用戶在這切換字體的整個過程當中,也幾乎不會感覺到字體的加載。這個預加載邏輯和以前處理濾鏡的方式一模一樣。
更值得一提的是,這種方式還解決了沒法知道字體文件何時加載成功的邏輯。
咱們都知道存在 DOM 標籤中的文字,若是是動態加載的字體,當字體文件加載好以後,設定了相關 font-family
的字體會自動從新渲染成對應的字體。
可是繪製在 Canvas 中的文字,在字體文件加載後,須要手動觸發渲染。但是犯難的問題在於,咱們如何才能知道一個字體文件是否加載成功?對於這個問題是有一個 「 Web Font Loader 」的庫能夠解決的。然而當咱們採用了預加載五個字體的邏輯以後,這個庫的意義就不大了。由於用戶在切換字體的時候,下一個字體可能早就已經加載完成,也就不會出現當前字體動態加載的問題。
固然有一些極端的狀況就是用戶在切換字體的時候按鈕屢次點擊,而且這個速度超過了咱們預加載 5 個字體文件的速度。或者在某些網絡相對沒有那麼好的地區,Google Font
加載比較慢。這個時候會出現當前字體對應不上「 會顯示默認字體 」。對於這個問題咱們的處理的方式是不處理。
由於用戶其實並不知道哪一個編號對應哪一個字體,即便出現了字體編號和字體不匹配的問題,用戶也是很難發現的,可能會覺得這個編號的字體就是和默認字體長得很像。當用戶點完一圈再次回來的時候,這個字體就頗有可能加載好了。
context.fillText(text, x, y [, maxWidth]);
複製代碼
canvas
文本繪製API
text
:須要繪製的文本x
: 開始繪製的橫座標y
: 開始繪製的縱座標maxWidth
:文本顯示寬度(文本放不下會水平方向壓縮)用 Canvas 繪製的文本,並不會像 DOM 標籤那樣超出以後會自動換行。想要實現自動換行,須要手動計算,而後追行繪製。好比你有一行文字 你是個人小蘋果
, 文字大小是 16px
,行高是24px
,Canvas 畫布的寬度是 4*16px
。就須要將按照每行最大 4 個字爲一組逐行繪製。
context.font = '16px STheiti, SimHei';
context.fillText('你是個人', 0, 0);
context.fillText('小蘋果', 0, 24);
複製代碼
圖1 / 圖2 / 圖3
國內和海外書封,最大的不一樣就是對於文字的處理。而這不一樣的緣由來自於中文和英文自己的差別。 簡單的說就是中文可能一個成語搞定的事,英文須要一整句話。
因此多數狀況下,做者的書名一行是放不下的。因而咱們只能反向思考,基於畫布的寬度,和一行文本的個數動態去計算文字的大小「 圖1 」。
字號 = 取整(畫布寬度/一行單詞個數);
複製代碼
當用戶點擊底部的空格區域添加換行符的時候,選取字數最多那一行做爲咱們上述公式的被除數,用來計算咱們的基礎字號,其它行也基於這個字號進行繪製。
固然咱們計算的字號不可能會無限大,因此咱們會給基礎的字號一個最大值,當計算出來的基礎字號超過這個最大值的時候,咱們會以這個最大字號做爲咱們的基礎字號「 圖3 」。
然而這就會出現另一個問題了,就是大多數的英文書名一行顯示的時候,這個動態計算的文字的字號都偏小。這就直接致使,大多數用戶在剛進入這個頁面的時候,都會看到這個並不優雅的狀態「 圖1 」。
爲了解決這個問題,在用戶首次進入文字排版的頁面時,會自動在文本正中間的最近的空格處,默認幫用戶添加一個換行符。雖然並非完美的解決,但至少能保證大多數的用戶首次進入的文本可讀性「 圖2 」。也能告知用戶,咱們的交互形式是利用底部空格進行換行,是一個一箭雙鵰的辦法。
單行文字起始點縱座標 = 基準點起點縱座標 + 行號 * 行高;
複製代碼
前面有提到,咱們在繪製 Canvas 文字的時候,還須要提供起始點的橫縱座標。在字號相等的狀況下咱們的起始點縱座標只須要經過上述公式就能夠實現。
行高 = 字號 * 1.5;
複製代碼
原本一開始想經過這個公式來處理文字的行高「 這是網頁中最經常使用對於行高的處理方式 」。然而在英文這個錯綜複雜的字體系列下,這個公式顯得是那麼的蒼白。
由於英文不像中文那樣是四四方方的,英文中有視覺上偏上的字母 「 l 」 , 也有視覺上偏下的「 g 」 。即便是同一個字體,一樣的字號行高下,你也能夠看到,第一列的文字發生了重疊,第二列顯示還好。因此在大小寫混排的狀態下,咱們是很難給到某一個具體到行高來規避這樣的問題。
此時細心的同窗可能發現,後面的兩列由於都是大寫好像這樣的問題就行了不少。因此咱們和設計師約定書封標題默認都是大寫,然而問題尚未結束。
由於咱們的字體用戶是能夠自由替換的。即便其它條件都一致的狀況下,用戶看到的效果仍然可能會是千差萬別。
行高 = 字號 * 1;
複製代碼
在通過屢次的調整和視覺對比以後,咱們最終選擇了這個公式來做爲咱們行高的計算方式。固然這中間也麻煩設計師捨棄了一些特別違和的字體。
原本這個對齊方式若是隻有左中右三種方式的話是沒有什麼好講。由於 Canvas text-align
API 自帶這三種對齊方式。
難就難在第三個 AUTO 的這種對齊方式,這實際上是一種相似海報中經常使用的藝術表現手法,上面的四個圖都是在 AUTO 模式下,只是調整了文字的換行實現的。能夠看到這個效果是比左中右這三種常規方式更加生動的「 爲優秀的設計師點贊 」。
相信你們經過上面的示意圖也能夠看出其中的邏輯。就是咱們每一行的字號是基於上一節提到的公式單獨計算的。簡單的說就是每行字數越少字號越大「 固然會有一個最大值 」。
單行文字起始點縱座標 = 基準點起點縱座標 + 以前每一行的行高;
複製代碼
然而如今由於每一行都是單獨計算,因此每一行的字號都不同,對應的行高也不同,就得把全部行高都紀錄下來,在逐行繪製。因此在 AUTO 模式下文字起始點的縱座標就變成了上述公式。
圖1 / 圖2
對於文本顏色,一開始我覺得,只須要設計師給我一個文字顏色列表就能夠了。然而事實仍是想得太簡單了。
好比設計師給個人顏色列表第一個顏色是白色,當用戶選擇瞭如圖2那種偏向純白的背景,若是仍是用白色做爲咱們文字的顏色,就幾乎看不清的。因此咱們須要有一個邏輯去針對不一樣的背景切換默認的文字顏色。
這邊給你們推薦的一個庫是 Color Thief。如上圖所示,就是給這個庫的 API 提供一張圖片,它就會返回給你這張圖的配色表。而且這個配色表的長度你是能夠定製的。
const isWhite= (r + g + b) / 3 < 128*1.3? false: true;
複製代碼
咱們怎麼基於這個庫去判斷一個背景圖是偏深仍是偏淺呢?很簡單,咱們取出這個配色表的第一個顏色,也就是這張圖片中的主色。讓後將這個顏色的 rgb 的色值取一個平均值,若是這個平均值小於 128*1.3 咱們就近似認爲這個圖是偏深。你們可能會問,128 不才是256 的一半嗎 ?爲啥咱們這裏還要再乘以 1.3 。
其實這個很簡單,在 rgb 平均值在 128 附近的時候,白色和黑色文字其實都是能夠看得清的。可是在這個邊緣,咱們更但願用戶是看到的是白色文字。至於爲啥是 1.3,其實就是咱們本身一張圖一張圖去試,大概以爲在這個閾值下,比較符合咱們指望的效果。
這裏 Color thief 對於圖片配色表的計算和以前濾鏡邏輯有着相似計算邏輯,就是咱們提供的圖片尺寸越大,這個計算的時間就會越長。原本咱們這裏要的就是一個模糊的值,因此咱們提供給 Color thief 的也是一個縮略圖。
到了這裏,咱們只是粗略的解決了用戶字體設置頁面的默認字體顏色問題。對於整個顏色列表要怎麼設置也是一個問題。
文字顏色列表 = 黑+白+8個配色+4個百搭顏色
複製代碼
咱們用 Color thief 取整個圖片的 9 個配色,而後用第一個顏色做爲咱們評判默認文字顏色是黑是白的標準。而後加上剩於的 8 個配色和設計師給到的 4 個百搭的顏色,組成咱們的文字顏色列表。
你們可能會好奇,這裏爲啥咱們直接用背景的配色做爲了咱們文字的顏色?這個方案是,張鑫旭老師在國內書封項目中提出的。
不難理解,就是圖片配色裏面的顏色和圖片自己搭配纔不會有太大的違和感。其它的顏色,有可能單獨看會很好看,可是放到一個不搭的背景裏面反而會顯得奇怪。因此後面設計師給到的顏色也是選的比較百搭的顏色。
篇幅關係,這邊着重挑出了幾個我認爲值得給你們分享的技術難點。另外還想給你們分享的是一點點項目心得。
剛拿到項目的時候,其實我最擔憂的是圖片的濾鏡和圖片紋理的疊加不知道要怎麼實現。由於這部分在我以前的項目經驗裏面是空白。然而在我實際使用的時候,這部分其實基本上就等同於調用一下 API 而已,固然這個和由於有張鑫旭老師的加持也有很大的關係,幾乎這方面的問題都能在他的博客中找到。真正比較難的仍是在想要提高用戶體驗這個點上。
一開始由於本身的一些既定思惟,誤覺得在瀏覽器端處理英文應該會比中文容易不少。畢竟中文字體動輒幾兆的文件大小在那兒擺着。然而在實際開發中,才知道由於英文語言特性,對於這種須要精細化處理的地方會有不少的坑。簡單的說,在某種字體下,你這整個邏輯均可能被推翻。在這一點上,可能仍是要略微捨棄技術追求,和設計師商量,看看是否能夠捨棄掉這樣的字體。
技術永遠只是你從 A 點到 B 的的工具。到達 B 點纔是你核心的目的。