說到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渲染又有所不同。
transf