<high performance web sites> Put Stylesheets at the top
Rule5: Put Stylesheets at the top
在yahoo!一个运行门户的团队在页面上增加一些DHTML的特性,前提是他们尽量保证这不会对页面的响应时间有不利影响. 在那些复杂的DHTML特性中,一个用来发送email消息的弹出div,它不是真正的属于此页面的渲染部分. 这是因为他只有在页面已经加载完成并且用户点击发送email消息的button后才能被访问. 既然它对页面的渲染是无用的,那么前台后端工程师把弹出框中的CSS放到一个外部的stylesheet中,然后通过在页面底部的一个链接tag来加载, 期望达到在页面最后加载,加快页面加载.
这么做背后的逻辑是有意义的. 许多其他的组件(images, stylesheets, scripts, etc.)是页面渲染所需要的。 因为组件都是按照页面文档中出现的先后顺序来下载的, 把这些DHTML的特性使用的stylesheet放到最后可以使更多关键的组件先于下载,结果就是加快页面加载.
事情真的会这样么?
在Internet Explorer中,页面的加载速度比原来的设计明显慢一些. 在解决这个问题加速页面加载的过程中,我们发现把DHTML特性的stylesheet放到页面文档的顶上,在HEAD里时,页面加载会更迅速. 这和我们期望的是相反的. 为什么把stylesheet放在首位,这样延迟页面关键组件的加载, 实际上提高页面加载时间呢? 更多的研究引出Rule 5,这个规则.
Progressive Rendering
前台后端工程师关心性能想让页面的加载能够进步; 换句话说, 我们想让浏览器显示任何内容都可以尽可能的快. 这对含有大量内容的页面或者在低网速条件下的用户来说是尤为重要的. 用户视觉反馈已经被研究和证明是非常重要的. Jakob Nielson,可用性工程师的先锋,在他的文章中强调视觉反馈是一个期间内重要的进步指标.
Progress indicators have three main advantages: They reassure the user that the system has not crashed but is working on his or her problem; they indicate approximately how long the user can be expected to wait, thus allowing the user to do other activities during long waits; and they finally provide something for the user to look at,thus making the wait less painful.This latter advantage should not be underestimated and is one reason for recommending a graphic progress bar instead of just stating the expected remaining time in numbers.
在我的例子里HTML页面是进步指标. 当浏览器加载页面进行的时候, 页面头,导航栏, 顶上的logo等等,都作为视觉反馈为等待页面额的用户提供服务. 这提高了整体的用户体验.
把stylesheets放到页面文档的底部的问题是这样做在很多浏览器中阻止了页面进一步的渲染. 浏览器阻滞渲染为了避免重画页面上的元素, 假如他们的styles改变. Rule 5对实际的加载那些页面组件的时间起很小的作用,但是对浏览器对组件的重新排序起较大作用. 事实上, 页面的视觉组件加载快,而又感觉慢的页面是具讽刺义的. 浏览器延迟显示一些视觉组件因为它和用户都要等待页面底部的stylesheet. 接下来的小节的例子将示范这个现象,我称他为”blank white screen”.
sleep.cgi
在建立模拟这个现象的例子的时候, 我开发了一个工具, 我已经发现的极其有效的显示组件如何延迟影响web页面: sleep.cgi. 这是一个简单的Perl CGI程序, 使用的paramters如下:
sleep
用来控制response的延迟时间,单位是秒. 默认值为0.
type
返回组件的类型. 可能的值是gif, js, css, html and swf. 默认值是gif.
expires
三选一的值: -1(返回一个已经过期的Expire Header头信息), 0(返回无Expire Header头信息), 1(返回一个在未来过期的Expire Header头信息). 默认值是1.
last
值-1返回一个最后修改时间Last-Modified header,并且这个时间和file的时间相同. 值0 不返回Last-Modified header. 默认值是-1.
redir
值1返回一个302的的响应,返回的URL和请求的URL除了少了redir=1外,其他完全相同.
第一个例子需要一些慢的images 和 慢的stylesheet. 我们可以通过sleep.cgi构造以下请求实现:
<img src="/bin/sleep.cgi?type=gif&sleep=2&expires=-1&last=0">
<link rel="stylesheet" href="/bin/sleep.cgi?type=css&sleep=1&expires=-1&last=0"> image和stylesheet这两个都使用了expires=-1的参数, 这样response返回的Expires header是过期的. 这就防止这两个组件被cache, 以便你可以重复运行测试并且在每次都能得到相同的体验(我也在每个组件的url上添加了唯一的时间戳,防止缓存). 为了减少测试的变数,我特别指定了last=0来移除response里的Last-Modified header信息. 这里image请求有两秒的延迟(sleep=2), 而stylesheet只有1秒的延迟(sleep=1). 这可以保证任何的延迟看起来都不是针对于stylesheet的影响时间, 而是针对于阻止行为(就是这个页面要测试的内容).
为了能够夸大组件的response时间,使得这个过程尽可能的被显现. 我已经将sleep。cgi的Perl的源代码公开,这样其他人可以用它来做自己测测试(http://stevesouders.com/hpws/sleep.txt). 拷贝里面的代码到sleep.cgi的可执行文件中,并把这个文件放到你web server中的可执行目录中.
Blank White Screen
这部分内网将描述两个页面,不同点在于:是否把stylesheet放到的头部或者底部. 这样会使用户体验有何不同!
CSS at the Bottom
第一个例子我们示例了把stylesheet放到页面文档底部的伤害.
CSS在底部
http://stevesouders.com/hpws/css-bottom.php
注意把sytlesheet放到文档的底部可以延迟页面的加载. 这个问题很难被追踪到, 它只发生在Internet Explorer 并且依赖页面的加载方式. 当你试过这个页面后,你会注意到这个页面的加载明显变慢了. 当这发生的时候,此页面是完全空白的,直到所有的内容瞬间突然出现在屏幕上. 页面渲染的进展已经被阻碍. 这是非常差的用户体验,因为这里没有视觉反馈使用户放心她的请求已经被正常的处理. 相反的, 用户只有怀疑是否发生了意外. 这时候,用户放弃了你的网站并且转向到你竞争者的网站.
这里有三种情况的测试,把stylesheet放到文档的底部在IE浏览器中出现空白屏的现象.
In a new window
在刚测试空白屏的页面中,点击”new window”的链接,打开新窗口. 用户经常打开新窗口当她们要另外的网站的时候,比如在搜索结果页面到一个实际的目标页面.
As a reload
点击浏览器的Refresh按钮.这是一个正常的用户行为,这也是一种能引起空白屏的方式. 最小化和恢复窗口当页面加载时看到空白屏.
As a home page
设置浏览器的默认首页为http://stevesouders.com/hpws/css-bottom.php, 打开浏览器后就会引起空白屏. Rule 5对于想让用户把他的页面当作首页的团队来说是相当重要的.
CSS at the Top
为了避免空白屏的发生, 把stylesheet放到文档的头部. 我做了一个例子页面”css at the top”解决了所有情节的问题. 无论是在新窗口或者刷新或者作为一个主页的时候,页面渲染都正常进行.
css at the top
http://stevesouders.com/hpws/css-top.php
解决了前面提出的问题点.
html文档中有两种添加stylesheet方式: 用LINK tag 和@import rule. 一个LINK tag的例子如下:
<link rel="stylesheet" href="styles1.css">
一个使用@import rule的例子如下:
<style> @import url("styles2.css"); </style>
一个style块可以含有多个@import rule,但是@import rule必须在其他规则之前. 我曾经看到过一个例子, 所有的开发者花费时间去查找为什么一个使用@import rule的stylesheet没有被加载. 碰到这样的情况,我宁愿使用LINK tag. 除了简单的语法外,使用LINK比@import还有性能上的收益. @import rule会引起空白屏现象,甚至假如你在文档头里,正像下面例子中显示的那样.
CSS at the Top Using @import
http://stevesouders.com/hpws/css-top-import.php
使用@import rule引起一个问题,组件的下载顺序不可预期. 现在来看三个例子. 每个页面都含有8个http请求:One HTML page, Six images, One stylesheet. 这些组件在css-bottom.php和css-top.php页面中都是按照其出现的顺序下载的. 但是在css-top-import.php页面中,stylesheet最后被下载,因为他使用了@import rule. 结果是他出现了空白屏的问题,就像css-bottom.php这个页面.
……(略)
使用LINK tag把stylesheets放到文档的HEAD中. 那么为什么浏览器以这种方式工作呢?
Flash of Unstyled Content
空白屏现象是对于浏览器的行为的. 记住即使我们的stylesheet没有在页面渲染的时候使用.甚至当IE浏览器已经得到全部的必须组件,它仍旧会等待,直到那些不必须的 stylesheet全部下载完成后才开始渲染页面. 在页面文档中的stylesheet不影响下载时间,但是会影响渲染. David Hyatt已经解释了为什么浏览器会这么做:
If stylesheets are still loading, it is wasteful to construct the rendering tree, since you don’t want to paint anything at all until all stylesheets have been loaded and parsed. Otherwise you’ll run into a problem called FOUC (the flash of unstyled content problem), where you show content before it’s ready.
接下来的例子会示例这个问题.
CSS Flash of Unstyled Content
http://stevesouders.com/hpws/css-fouc.php
在这个例子中,文档从stylesheet中使用了一个CSS rules,但是这个stylesheet是放在底部的. 当页面在加载的进度中, text最先被显示, 接着是images. 最后,当stylesheet成功下载并解析后, 已经被显示的text和images被用新的style重新显示. 这就是在行为中的”flash of unstyled content”. 它应该是被避免的.
浏览器中的空白屏试图宽免那些犯错的前台后端工程师,他们把stylesheet放到了文档的后面. 空白屏阻止了FOUC问题的发生. 浏览器可以延迟渲染直到所有的stylesheet下载完毕, 但是引发了空白屏. 但是相对的,浏览器可以逐步的渲染最后重新渲染页面. 无论怎么做,这两者都是不完美的.
What’s a FrontEnd Engineer to Do?
那么如何避免空白屏和FOUC呢?
在”CSS Flash of Unstyled Content”的例子中,刷新不是永远发生的;他依赖于浏览器和你如何加载页面. 在早先的文中,我解释了空白屏只发生在IE浏览器上,当在新窗口中加载页面,或者刷新页面,或者作为首页. 在这些例子中,IE浏览器选择了空白屏的方式. 然后假如你点击一个链接,或者使用一个标签页,或者直接输入一个url,那么IE浏览器将选择第二种方式:FOUC.
David Hyatt,”Surfin’ Safari” blog, http://weblogs.mozillazine.org/hyatt/archives/2004_05.html#005496.
FireFox浏览器在这方面是一致的,它永远选择第二个方式(FOUC). 所有的例子在Firefox中的行为都是一致的: 逐步渲染. 对于首先的三个例子,Firefox工作的行为方式对用户是有意的,因为stylesheet对页面渲染是没有用的. 但是在”CSS Flash Unstyled Content”的例子中,用户是不幸的. 用户正好体验了FOUC问题因为firefox逐步渲染内容.
当浏览器的行为不一致时,前台后端工程师该怎么做么?
你可以在HTML规范文档中找到答案(http://www.w3.org/TR/html4/struct/links.html#h-12.3):
Unlike A, [LINK] may only appear in the HEAD section of a document, although it may appear any number of times.)
浏览器拥有支持惯例的历史,违反HTML规范是为了那些老的,不守规范的页面可以正常显示, 但是当他遇到处理stylesheet的位置时,IE和firefox浏览器都推动开发组织注意遵守规范. 违反规范的页面(在HEAD外放置LINK)仍旧被渲染,但是在一定程度上影响用户体验.
Put your stylesheets in the document HEAD using the LINK tag.