说到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等浏览器,以上的差异性也将减小甚至消失。