說到web效能,前端工程師很自然地反應是yahoo的30+條最佳化規則。這些規則可以將網頁載入從原來的幾秒甚至十幾秒較少到3s甚至1s以內。當一個完整介面展現在使用者眼前時,內容就通過不同的字型、圖片以及多媒體傳達給使用者。使使用者在1s內看到網頁和使使用者留在網頁上幾分鐘甚至幾十分鐘同樣重要。字型作為內容承載資訊的重要部分,若使用不“適當”的字型或者字型由於渲染等緣故對使用者不友好,則會(有可能)造成不必要的使用者流失。本文介紹瀏覽器的字型渲染,希望還未接觸字型渲染的同事能通過本文對字型渲染有所瞭解。
首先看下不同瀏覽器下的截圖:
很明顯地看出,Chrome35截圖中的非橫豎筆畫較IE11和Firefox30截圖中的有明顯的鋸齒。
理想的字型其邊緣過渡平滑,而在螢幕上顯示時,需要將字型柵格化(rasterization)為一個個像素點。採用黑白(black and white)渲染無法體現字型的細節之處,會造成了邊緣呈現鋸齒狀(jagged)的不平滑。為瞭解決這個問題,字型渲染引擎採用了以下方法進行平滑(Antialiasing):灰階(grayscale)渲染、亞像素(sub-pixel)渲染。
渲染方法
灰階渲染是一種通過控制字型輪廓上像素點的亮度達到字型原始形狀的方法。
亞像素渲染利用了LCD屏(液晶屏)中每個像素是由R、G、B三個亞像素的顏色和亮度混合而成一個完整像素的顏色這一原理,將字型輪廓上的像素點由三個亞像素體現以達到原始形狀的方法。與灰階渲染相比,解析度在垂直方向放大了三倍,因此渲染效果更好,但是所耗記憶體也更多。因此在手機屏上,為了減少CPU開銷,並未採用該字型渲染方法,而是採用的grayscale渲染。
作業系統中的渲染API
作業系統OS提供了支援不同的字型渲染方法的API。在windows下是GDI(Graphics Device Interface)和DirectWrite,OS X下是Quartz。
GDI分為GDI Grayscale和GDI ClearType。前者為灰階渲染API,後者是亞像素渲染API。由於GDI ClearType並未對字型進行垂直方向的平滑,因此當字型較大時會出現邊緣不平滑的情況。為了彌補GDI ClearType的不足,MS實現了DirectWrite API,它在GDI ClearType的基礎上增加了垂直方向的平滑。
但是!字型渲染的API都是由瀏覽器廠商自己選擇的!
使用同一顏色,感官上的顏色深淺為:黑白渲染>grayscale>sub-pixel。
Chrome35/36採用的是GDI ClearType,因此在字型較大時邊緣會出現毛刺,而FF30採用的DirectWrite則沒有此類問題。如下圖所示:
根據以上兩圖可以發現,Chrome的字型渲染效果沒有Firefox的好。為縮小Chrome與FF/IE的差異,一種方法是使用-webkit-text-stroke:0.5px;如果使用1px的話就有點粗了。
雖然該hack使Chrome下字型的邊緣有所光滑,但字型在webkit核心瀏覽器中看上去“模糊”了。
常用字型在瀏覽器中的渲染情況
從使用Microsoft Yahei, Tahoma和Arial字型可以看出,在Chrome35中,由於轉換成GDI Grayscale渲染,49px的字比48px的邊緣光滑很多。字型較大時,GDI Grayscale比GDI ClearType具有更好的渲染效果。
在實際中會遇到在如Tahoma/Arial字型下17px和18px的“字重”有明顯差別。這個現象涉及到字型設計,簡單地說就是在瀏覽器渲染字型之前,字型本身會調整字型和筆畫所佔像素。想瞭解更多細節,請參考a closer look at TrueType hinting。
啟動Chrome中的DirectWrite
在chrome://flags下啟用實驗性DirectWrite字型渲染系統:#enable-direct-write會使48px以下的字型均為sub-pixel渲染。僅適用於測試環境!(需重啟Chrome)
Chrome37 beta將該特性移除實驗特性,並將在Chrome37中實現在windows下使用DirectWrite API對字型渲染,相信在Chrome37中以上提到的瀏覽器字型渲染差異會得到解決。
字型格式在不同瀏覽器下的渲染
font-smoothing: subpixel-antialiased | antialiased;
對應為sub-pixel和grayscale。
已廢止!
font-weight: bold;
Chrome35, IE7/8:與normal字型渲染一致(包括數字和字母);
IE9+/FF30:grayscale,部分簡單中文字型保持原有渲染。
font-style:italic;
Chrome35, IE7/8:與normal字型渲染保持一致;
IE9+/FF30:sub-pixel渲染。
opacity: 屬性值<1
Chrome35:sub-pixel渲染的字型“降級”為grayscale渲染;
IE9+/FF30:保持不變
transform 2D
transform: rotate(n);
n=90*k deg(k=整數), 保持未旋轉的字型渲染;
others,sub-pixel渲染。Chrome35在48px+為grayscale渲染。
transform: scale(x,y); 與字型大小m有關。以simsun為例,具體如下:
Chrome35:
x*y*m<12, Chrome35 sub-pixel渲染;
12<x*y*m<=48*48=2304 sub-pixel渲染; x*y*m>2304 grayscale渲染,12px-24px存在一定範圍的黑白渲染(tahoma是12px-16px)。
FF30、IE9+:
m*m*n<=99*99=9801 sub-pixel渲染; m*m*n>9801 grayscale渲染。
transform: skew(n);
n=90*k deg(k為整數), 保持未傾斜的字型渲染;
others, sub-pixel渲染。Chrome35在49px+為grayscale渲染,IE9+, FF30在100px+為grayscale渲染。
transform 2D & transition
運動中:以12px為例: 在Chrome35和FF30下,運動中的字型均是grayscale渲染;
運動結束:Chrome35, FF30, IE9+均為sub-pixel渲染。
從運動開始到運動結束產生了兩次渲染變化(sub-pixel到grayscale,grayscale到sub-pixel)會出現“閃現”的現象。
Transform 3D
當元素進入GPU中渲染時,在Chrome35+中的字型為grayscale渲染,IE11和FF30保持sub-pixel渲染不變。若transform值函數(如translate3d(), scale(), rotate()等)中的參數為非整數,會導致字型模糊。在使用iScroll類比滾動的項目中會出現字型模糊。以下是對該問題的一個還原:
-webkit-transform: translate3d(1.5px, 1.5px,0);
-webkit-transform: translate3d(1px, 1px,0);
Chrome 36中使用transform不需要-webkit-首碼了!
為了防止以上模糊情況的出現,可以通過JS中的Math.round()/Math.ceil()/Math.floor()等函數使其為整數。
當加入perspective()值時,Firefox30渲染又有所不同。
transform: perspective(1px) translate3d(1.5px, 1.5px,0);在FF30中作用的元素為grayscale渲染。
transform: perspective(1px) translate3d(1px, 1px,0); 在FF30中作用的元素為sub-pixel渲染
E11均為sub-pixel渲染。
此情況下運用opacity<1的聲明,IE10+為grayscale。
Chrome transform: perspective()對文檔後面元素的影響
Chrome35+,當transform中聲明有perspective()時,若文檔後面聲明transform的元素進入該聲明作用的元素的layer-borders的地區時,仍然會受到該聲明的影響。聲明作用元素的溢出部分同樣會對後面元素造成影響。合成渲染層內部文字進入GPU渲染。
啟動Show composited layer borders,在Chrome中測試的結果截圖為:
總結
字型渲染與作業系統、瀏覽器、字型格式以及CSS屬性有關。本文重點羅列了一些在Windows Chrome中字型渲染與FF/IE的差異和對應緩解/問題的解決方案。Chrome在字型渲染上目前落後於IE/FF,但之前官方部落格稱Chrome 37將實現在windows下使用DirectWrite API,Chrome在字型渲染方面將趕上IE/FF等瀏覽器,以上的差異性也將減小甚至消失。