网页设计师和前端开发者看的前端性能优化
资源预取
和DNS预取一样,也可以顺便对你的站点需要的其它资源进行预取。为了弄清楚我们想要预取哪些资源, 首先我们需要了解浏览器通常会在什么时候以什么方式对资源发出请求。
CSS中引用的Web字体和图片表现基本相同;浏览器在碰到需要它们的HTML时开始对它们进行下载。就和我在前面提到那样,浏览器非常聪明,这又是一个例证。想象一下,浏览器一看到下面的CSS声明就开始下载其中所引用的图片:
1 2 3 4 | .page--home { background-image : url (home.jpg); } .page--about { background-image : url (about.jpg); } .page--portfolio { background-image : url (portfolio.jpg); } .page--contact { background-image : url (contact.jpg); } |
如果浏览器不是等碰到需要这些图片的HTML再下载它们,那么访问主页就会立即下载所有这四个图片。这会造成浪费,所以浏览器一定会确保在需要这些图片时才会开始下载它们。所以,这里有个问题在于,图片下载直到很晚才会开始。
如果我们可以完全确认某个CSS图片肯定会在每个页面都会用到的话,我们就可以用个小把戏让浏览器早早下载好这个图片,无需等到让浏览器碰到需要使用该图片的HTML才开始下载。想做到这一点也非常简单,但所用的方法可能会有点糙,就看你怎么弄了。
比较糙的方法和大多数笨拙的万全之法类似,就是在每个页面放置一个隐藏的<div>,在该div中使用带有空的alt属性的<img>标签。我在CSS Wizardry项目中的精灵中就是这么干的;因为我知道,每个页面都要使用该精灵,所以我就通过在HTML中对其进行引用对它进行预取。浏览器处理内联(inline)<img>的方式非常好,浏览器会早早地对它们进行预取,所以通过让浏览器将我的精灵作为HTML中的<img>进行载入,浏览器就可以在使用需要精灵的CSS之前将其下载好。通过首先在我的HTML中引用该精灵(隐藏起来的),我就能够抢先把精灵下载好。
还有第二种方法比较”优雅”,但会让人有些困惑。它和DNS预取的例子非常相似
1 | < link rel = "prefetch" href = "sprite.png" > |
这会显式地告诉浏览器,马上开始预取我的精灵图片,而不要考虑在它处理CSS时可能会做的任何决定。
令人感到困惑之处在于有两篇文章似乎有不同的观点;基于来自MDN的这篇文章,貌似这种预取指令只是示意浏览器仅在它空闲时才有可能会对href所指的资源进行预取。然而,与此矛盾的是,来自Planet Performance的这篇文章貌似在说,如果浏览器支持rel=”prefetch”的话,它就一定会预取href中所指的资源,并没有提及是否要在浏览器空闲时才进行预取。我在WebKit的Inpsector中的瀑布图中所看到的情况是后者说得是对的,但是在打开Developer Tools的情况下(薛定谔测不准。。。)WebKit的表现及其怪异,我就观察不到预取动作的情况了,这也就是我说,我无法100%保证我说的是对的。要是谁能解释清楚这方面的情况,我将不胜感激。
我在前面说过,字体和图片表现非常相似,上面所说的规则同样也适用于字体文件,但你无法使用隐藏的<div>载入字体文件(你需要使用预取link)。
1 | < link rel = "prefetch" href = "webfont.woff" > |
所以,基本可以这么说,我们这里所作的一切,只能算是让浏览器提前下载资源的”小把戏”而已,耍了小把戏之后,在浏览器碰到要使用CSS的时候,其中所引用的资源就早已下载好了(或者至少已经在下载中了)。 漂亮极了!
CSS 与性能
许多建议说,如果你在使用资源域名,你应该由它们提供所有静态资源服务;包括CSS,JS,图片等等。
但是在工作中我们发现一件事,那就是你不应该由一个资源/子域名提供CSS服务…
还记得先前我们讨论CSS块渲染吗?浏览器想尽可能快的获得CSS,直到不能更快;CSS位于你的关键路径。你的关键路径是用户页面请求与之后实际看到页面之间的必要的旅程。因为它阻塞了渲染,所以CSS位于关键路径,而JS和图片不是。你会希望在关键路径上尽可能快的加快这个旅程,这就意味着不能有DNS查询。
实际工作中,我们搭建了一个网站,在某个阶段性的环境中它由同一台主机(如foo.com)提供资源服务,但到了使整个环境支持更加繁忙业务的时候,我们开始由s1.foo.com与s2.foo.com提供资源服务。这意味着所有的图片,JS,CSS,字体等等都来自于不同的域名,由此便引起了DNS查询。这里的问题在于,由于空的缓存,为了获得CSS文件而需要执行DNS查询,这实际上使得关键路径速度彻底慢下来。我们的图片大多数会模糊,这暗示着有理论上不应该有的延时;最佳实践要求应该将资源分布于在子域名上,对吗?但不包括CSS。DNS查询占据了大量的时间,进而延迟了页面的渲染。
因为有这种渲染阻塞阶段,CSS是性能最坏的敌人之一,正如Stoyan Stefanov阐述的那样 。而且也很有必要注意到浏览器在它开始渲染页面之前将下载所有的CSS。这意味着即使浏览器仅仅在屏幕上渲染页面,也要请求print.css。任何只是基于一种媒体查询的样式表(如<link rel=”stylesheet” media=”screen and (min-device-width: 800px)” href=”desktop.css”>)都将会被下载,即使并不需要它们。
即便如此,Andy Davies 告知我WebKit实际上提高了CSS下载的优先级,以便只有渲染页面需要的CSS先到达,而其他的样式,如print.css尽可能的延迟。漂亮!
知道这些关于CSS的信息已经允许我们做出一些决定,这些决定全部基于CSS阻塞渲染,要全部被请求,以及它位于关键路径的知识:
- 永远不要从一个固定/资源域名提供服务 因为这会引起DNS查询并进一步延迟渲染。
- 先提供服务 因此浏览器可以继续忙下去。
- 合并它 因为不管怎样浏览器会获取所有CSS,你最好将所有这些压缩于一个HTTP请求。
- 压缩并简化它 以便浏览器需要下载的少一些。
- 缓存它的一切 以便上述的过程尽可能少的发生。
CSS位于关键路径,因此你需要尽早先解决它,它阻塞渲染就意味着降低了用户的性能体验。 把CSS移到子域名会损害性能。