原文:Pro HTML5 Performance
协议:CC BY-NC-SA 4.0
一、简介
不久前,在面试职位空缺的候选人时,我们发现我们的开发伙伴在性能和可伸缩性领域存在一些明显的知识差距。虽然许多开发人员完全精通他们选择的服务器端语言,但他们对 HTML5 和 CSS3 的学习似乎只达到了轶事般的水平。(所谓“轶事水平”,我们的意思是他们已经看到了 HTML5 和 CSS3 的例子——或者可能已经阅读了 HTML5 的新方面的概要——并从这些模式中得出结论,但错过了它们背后的一些更深层次的意义。换句话说,我们发现很多人能告诉我们如何做某事,但不能告诉我们他们为什么想做某事。更重要的是,他们不知道他们喜欢的技术如何让代码执行得更好,或者减少他们完成工作所花的时间。在这种情况下,我们看到了一个很好的机会来帮助其他开发者提升他们的前端游戏,我们决定写这本书。
我们两人是在为一家电子商务业务仅次于亚马逊的财富 50 强公司工作时认识的。换句话说,我们要看看在标尺的高端,什么可行,什么不可行。此外,我们所在的团队负责编写一个在公司网站上使用的框架,这个网站包含数万个页面。此外,在向 MVC 转换期间,我们是从零开始的。因此,虽然我们的代码必须对每个访问者表现得非常好(每月有 8000 万访问者),但它也必须足够高效,以满足公司内许多团队的需求——实际上有几十个客户团队。
我们希望在本书中传递的东西来自于在这一努力中获得的经验教训和我们的经验提供的独特视角:对 HTML5/CSS3 性能的更深入理解,以及一些有望改变游戏规则的模式,这些模式将把您的前端技能提升到下一个水平。我们认为我们甚至可能会看到 web 开发的范式转变,至少对于大型复杂的网站来说是这样。
带有工作代码示例的实时网站
为了让尽可能多的读者了解本书中涉及的概念和技术,我们创建了一个现场站点,其中包含本书中显示的工作代码示例,以及一个响应迅速的电子商务概念验证。您可以在 www.clikz.us
找到示例网站
图 1-1 显示了我们的样本现场。
***图 1-1。*clikz . us 网站(iStockphoto.com/Ociacia)
期待什么
让我们非常清楚这本书是什么,不是什么。“HTML5”是一个负载如此之大的术语,它可能会产生误导,尤其是当它出现在书名中的时候。这个术语可以表示超出其技术含义的各种各样的事情,这只是 HTML 的一个特定(截至 2012 年,最新)版本。它还被用来描述浏览器制造商推出的许多新技术:支持 CSS3、原生音频和视频、Canvas、WebSockets、应用缓存、本地存储、索引数据库、文件 API 和地理定位等。
虽然所有这些事情都令人兴奋,非常值得了解,但本书主要关注以下几个方面:
- 理解浏览器(现代和传统)如何处理代码,以及如何利用这些知识。
- 交付极高性能的 HTML5(在 HTML 最新版本的意义上)、CSS3、JavaScriptJavaScript 封面主要为不支持 HTML5 和 CSS3 的浏览器提供了退路。
- 向您展示新的模式和技巧以添加到您的食谱中,这些模式和技巧可以满足大量电子商务和一般网站的需求,从而为您的访问者提供良好的体验,并使您作为前端开发人员的工作更加愉快和高效;
- 将服务器端逻辑集成到真正强大和通用的前端结果中。
- 为您提供开发前端代码的独特视角,最大限度地发挥每种技术的优势,并清晰地分离关注点,使您的代码具有良好的可伸缩性和持久性。
定义高绩效
谈到性能,我们定义了四个重点关注的领域。我们从性能的传统定义开始,因为它与页面负载有关,但后来我们发现了更多的性能提升,导致了以下几种性能:
- 页面加载时间
- 浏览器性能
- 网路性能
- 开发者表现
页面加载次数
大多数人将网站性能与页面加载时间联系在一起。这是一个合理的观点,因为缓慢的页面加载会产生挫折感并增加跳出率(访问者离开网站)。此外,随着 Google now 提供部分基于页面加载时间的页面排名,您已经获得了关注这种性能定义所需的所有动力。
浏览器性能
现代浏览器真的很注重性能。从更快的 JavaScript 引擎到优化的解析算法,再到 CSS 处理的复杂动画——这是一个全新的领域。因此,如果您的代码没有优化以利用这些进步,您可能会错过一些重要的性能提升。
网络性能
带宽是每个公司都想控制并最终尽可能限制的费用。我们展示了减少带宽的技术,同时仍然使页面看起来一样好(如果不是更好的话),并且至少在访问者的浏览器上呈现得一样快。
开发商业绩
当我们说我们不喜欢不断地重写一堆相似的代码,更糟糕的是,不得不月复一月、年复一年地维护它时,我们认为我们代表了大多数开发人员的心声。本着这种精神,我们分享一些技术和方法,让您可以在多种情况下重用代码。他们的核心概念是从干净、灵活的 HTML5 作为内容容器开始,然后利用 CSS 做它最擅长的事情,即内容的可视化呈现。
我们还分享了分离代码的方法,以实现最大程度的重用和最小程度的名称冲突。如果你是唯一的开发人员,这种方法不仅会有帮助,而且如果你是在一个网站上工作的团队成员,这种方法也会大放异彩。
除了减少重复性和快乐的一天之外,由此产生的性能还产生了一个巨大的好处:节省的时间和用于表达各种各样的演示目标的代码的减少让您可以对您的代码进行镀金(即,优化,使其更加健壮和可靠,或者改进)。在高要求的工作环境中,这通常是被忽略的一步。我们都告诉自己以后会回来真正优化我们的代码,但我们很少有机会。
正如我们在本书后面讨论按钮控件时指出的那样,这看起来像是为一个按钮编写了很多额外的代码,直到你意识到你再也不需要制作另一个按钮。
响应式/适应性设计
我们还涵盖了响应性/适应性设计技术。这是你的网站适应或响应不同设备(智能手机、平板电脑等)的想法。).我们将这些技术包含在一本关于性能的书中,以介绍“一个代码库”的概念。不用为智能手机和平板电脑分别编写一个网站,你只需编写一次代码,然后让它适应。无论是对于第一个版本还是后续的维护,这都是一个巨大的开发人员性能提升。
电网系统
CSS 网格系统现在非常流行,理由很充分:一个网格系统可以节省大量的时间和许多令人头疼的问题。我们将向您介绍网格系统的内幕,并向您展示如何使用网格系统来减少您需要的 CSS,以及如何充分利用它,结合响应式设计,来真正加快开发速度,并使您的页面更加一致,更不容易出错。
对 CSS 的更深入了解
我们希望,到本书结束时,你会对 CSS 做什么以及为什么做有一个更深更清晰的了解。我们提出了一些先进的技术;你可能会对他们给你的力量感到惊讶。我们还向您展示了如何利用现代 CSS 技术,同时优雅地适应旧浏览器。作为开发人员,我们希望帮助您利用 CSS3 的所有性能增强和强大功能!然而,我们仍然希望为使用旧浏览器的访问者提供良好的体验。我们将向您展示如何在相同的代码库中完成这两项工作。
二、开发原则
发现一些有用的原则后,我们在整本书中反复使用它们(当然,也贯穿于我们在书外的工作)。这些原则是我们在书中将要说的一切的基础。因此,我们认为我们应该在这里描述它们,然后再继续讨论更具体的主题。以下部分展示了我们所采用的设计和开发原则:
- 现代浏览器性能代码
- 使用 CSS 管理边界
- 拥抱渐进增强
- 拥抱关注点的分离
当我们在大型开发团队中工作时,这些原则让我们为访问我们网站的人、为我们自己、为我们的同事实现最好的性能。其中一些原则(尤其是使用 CSS 来管理边界)也让我们避免了一些最大的跨浏览器难题。
现代浏览器性能代码
如果你想成为一名性能忍者,你必须了解浏览器是如何工作的(至少在广义上)。只有这样,你才能知道瓶颈在哪里,并围绕它们进行优化。图 2-1 显示了一个流程图,展示了你的代码(HTML & CSS)到你的访问者在浏览器中看到的最终呈现版本的过程。
***图 2-1。*浏览器正在处理的代码
首先,HTML 被解析成一棵 DOM 树,也称为文档对象模型(DOM)。这就是为什么当浏览器遇到一个页面时,第一件事就是下载该页面的 HTML 内容。另一个原因是 HTML 包含对定义页面的所有其他资源(样式表、脚本、图像等等)的引用。然后,通过将 DOM 和样式规则(由 CSS 生成,包括您提供的和浏览器自带的)组合成一个渲染树(或者在 Firefox 中是一个框架树)来创建第二棵树。从这个渲染树中,浏览器开始在屏幕上显示或绘制元素。这幅画从左上角开始,从左到右,从上到下流动。
您可以通过两种方式获得性能:
- 减少 HTML 中的元素数量。
- 限制重画。
减少 HTML 中的元素数量
通过减少 HTML 元素的数量(这些元素必须首先被解析成 DOM,然后再被解析成渲染树),你可以让浏览器更快地到达显示端点(如图 2-1 所示)。减少 HTML 元素最简单的方法就是不要用它们来实现样式目标,而要用最少的 HTML 来实现设计目标。
记住关注点分离的原则,我们将在本章的后面详细讨论。让 HTML 包含内容,让 CSS 包含表示。这样做可以提高客户端的性能,并且由于更易于维护,还可以提高开发人员的性能。
极限重绘
虽然减少 HTML 中的元素数量有所帮助,但是限制浏览器必须重绘(或重绘,因为操作有时是已知的)元素的次数通常更有帮助。Web 开发人员通过更改 DOM 或已显示元素的样式来强制重绘。
变更的性能成本取决于变更的范围。现代浏览器被设计成只重画必要的东西。因此,虽然更改元素的位置或插入新元素会导致大范围的重绘(因为它会影响同级元素),但对背景颜色的样式更改只会导致该元素(及其子元素)的重绘。
在对 DOM 进行修改或重新设计元素时,应该考虑两个问题。第一个问题是 DOM 内部变化的深度。DOM 树越深,变化越孤立;因此,您应该尽可能在树的最下面进行更改。第二个也是更重要的问题是,如果你要对 DOM 做几个改变,要一次完成,而不是一次只做一个。由于第二个问题,在修改 DOM 时,CSS 可能是您最好的朋友。
例如,如果你想在双击时改变一个元素的宽度、背景颜色和文本颜色,你可以使用类似于清单 2-1 中所示的 JavaScript。
***清单 2-1。*创建多个重绘事件的 JavaScript】
`I’m an Example
`在这个例子中,我们一次设置一个元素的样式。首先,脚本将背景颜色设置为红色(强制重绘),然后将宽度设置为 200px(强制第二次重绘),然后将文本颜色设置为白色(强制第三次重绘)。虽然您仍然可以以多种方式使用 JavaScript 来将这些样式更改合并到一个调用中,但是更容易且更易于维护的方式是使用 JavaScript 来设置包含所有这些属性的 CSS 类。这样做会将所有样式更改合并到一次重绘中。清单 2-2 展示了一个只强制一次重绘的重造型的例子。
***清单 2-2。*创建单个重绘事件的 JavaScript】
`
I’m an Example
最后,您应该将 CSS(包括对外部样式表的引用)放在 head 元素中,并将脚本放在 body 元素的底部。因为浏览器可以在完全解析 HTML 之前开始呈现元素,所以将 CSS 放在头部可以确保这些元素的样式正确。对性能来说更重要的是,您不希望元素必须被重绘,因为您在元素呈现后放置了一个样式声明。此外,无意中看到物品移动也是一种不好的视觉效果。此外,因为浏览器必须评估 JavaScript 文件,将它们放在 HTML 的开头会延迟视觉元素的呈现,并给访问者一种页面加载较慢的感觉。
我们将在下一章讨论将 CSS 放在顶部,将 JavaScript 放在底部。我们还将在下一章讨论一些避免重绘事件的其他方法。
使用 CSS 来管理边界
正如我们将在本章后面的“都是盒子”一节中详细讨论的那样,浏览器将网页呈现为一系列的盒子,而这些盒子通常包含其他的盒子。因此,我们可以说浏览器的自然呈现模型是盒中盒。知道了这一点,明智的做法是安排你的布局与浏览器实现的框中框呈现模型一起工作,而不是与之对抗。
为了充分利用这种框中框的实现,最好的办法是安排每个元素或元素组,使其完全包含在一个框中。相反,糟糕的事情是有东西从你的盒子里伸出来。我们将用一个例子来说明好的和坏的做法。
首先,让我们定义我们在做什么。我们希望创建一个文章堆栈,在文章文本的左侧包含一个图像,在文章文本的上方包含一个标题,并让文章文本垂直延伸到任意高度。我们称之为“堆栈”,因为它在页面上堆叠元素。图 2-2 显示了期望输出的示例。
***图 2-2。*我们的目标产量
清单 2-3 显示了为这个文章堆栈提供内容的 HTML。
***清单 2-3。*我们文章栈背后的 HTML】
`
Chrome’s Evil Twin Brother
The logo is darker because they couldn’t make it look right with a goatee.
`清单 2-4 显示了一组 CSS 规则,这些规则将创建一个盒子,并把所有内容和我们想要的关系放在盒子里。
清单 2-4。 CSS 把我们的文章堆叠在一个盒子里
`.browserArticle
{
/* We set the position: relative so the absolutely positioned
element within uses this box to position itself /
position: relative;
width: 200px;
padding-left: 48px;
/ We set a minimum height in case there’s not enough content to make the box big enough
to house the image. We use the height of the image plus 3 for the top offset. */
min-height: 39px;
}
.subTitle
{
font-size: 18px;
}
.evilChromeLogo
{
background: url(img/evilChromeLogo.png) no-repeat 0 0;
height: 36px;
width: 38px;
position: absolute;
left: 0;
top: 3px;
z-index: 1;
}
.accentColor1
{
color: #1C70AD;
}`
在清单 2-2 中的关键风格是.browserArticle
规则。它指定了 200 像素的宽度,没有高度,给我们一个 200 像素宽的盒子,它将扩展到其内容的高度。它还指定了 48 像素的左填充值。我们将使用这 48 个像素作为放置图像的地方。除了指定背景图像及其高度和宽度之外,.evilChromeLogo
规则还使用position: absolute
规则和left: 0 rule
将图像放在盒子的左边距上。以这种方式,我们创建了一个包含其边界内所有内容的盒子。这样,我们就不必考虑任何超出边界的内容可能会发生什么,因为那永远不会发生。
现在让我们看看创建相同布局的一种错误方法。我们仍然使用清单 2-3 中的 HTML 作为我们的内容来源。清单 2-5 展示了一种布局文章堆栈的不良实践。
***清单 2-5。*我们的文章堆栈有缺陷的 CSS
`.browserArticle
{
position: relative;
width: 200px;
** margin-left: 48px;**
/* Removed paddingleft setting */
}
.subTitle
{
font-size: 18px;
}
.evilChromeLogo
{
background: url(img/evilChromeLogo.png) no-repeat 0 0;
height: 36px;
width: 38px;
position: absolute;
** left: -48px;**
top: 3px;
** /* Removed z-index setting /*
}
.accentColor1
{
color: #1C70AD;
}`
这个清单的大部分与清单 2-4 相同。我们用粗体突出显示了这些变化。我们仍然在创建一个 200 像素宽的框,将文本放在框中,将图像放在文本的左边。不同的是,我们现在把图像放在盒子外面。browserArticle
类指定左边距而不是左填充。“evilChromeLogo”
类指定了一个 48 像素的左值(这个负数应该给你敲响警钟)。
问题是边距在定义边距的元素的框之外。填充位于定义填充的元素的框内。因此,虽然这两个规则集都适用于现代浏览器,但是清单 2-5 中的规则集更有可能在旧浏览器中遇到不一致。正如我们在下一节“拥抱渐进式改进”中所讨论的,您不希望给任何访问者留下不好的体验,即使他们使用的是过时的软件。
我们还发现,为整个文章堆栈定义一个框,然后设置“left: 0
”将图像放在左边距,这样更自然(Mike 说,“感觉很好”)。代码的意图更加清晰,代码比负偏移方法更易于维护。
这个例子说明了我们的信念,即标记应该
- 清楚地表达它的意图(也就是说,包含有意义的标记),这有助于我们的合作者知道我们在做什么。
- 为尽可能多的浏览器工作,省去了编写和维护额外跨浏览器代码的麻烦。
- 易于创建和维护,这在代码生命周期的后期增强了我们自己和团队成员的能力。
- 模块化,这使得重用成为可能。
我们应该多解释一下重用的目标。如果您编写的代码可以独立于上下文,它就可以被重用,因为它不受任何给定设置的约束。考虑购买按钮的例子。它是一个具有特定用途(支持购买)的界面元素,但它可能出现在许多不同的上下文中(例如产品详情页面、产品列表页面和特价页面)。将购买按钮模块化可以让我们把它放在任何我们想要的地方,而不必在每个地方都修改它。此外,因为我们没有用边界做任何奇怪的把戏,任何给定的模块在不同的情况下都更有可能表现良好。我们发现,一旦我们花时间在第一位置设置代码以供重用,达到重用可以让我们快速完成很多工作。
识别代码偏离这些目标的地方并不总是容易的。诀窍是观察任何使相邻框重叠的东西,包括向左的负偏移或向右的正偏移。
拥抱渐进式改进
渐进增强是这样一种实践,即让你的网站有一个所有浏览器都可以接受的基本设计,然后为日益现代的浏览器添加增强功能(也就是说,渐进地)。从 CSS/HTML 基础开始,让我们有一个可以在所有浏览器上工作的网站,并让我们有机会为支持 HTML5 功能的浏览器大大增强它的功能。清单 2-6 显示了一个 HTML 元素,它构成了一个简单例子的基础。
***清单 2-6。*一个简单的渐进增强示例的 HTML 元素
<div class="someClass"></div>
清单 2-7 展示了 CSS(展示了渐进增强)来样式化清单 2-6 中显示的div
元素。
***清单 2-7。*CSS 样式清单 2-6 通过渐进增强
.someClass {
width: 100px; height: 100px; background-color: #2067f5; background-image: -webkit-gradient(linear, left top, left bottom, from(#2067f5), to(#154096)); background-image: -webkit-linear-gradient(top, #2067f5, #154096); background-image: -moz-linear-gradient(top, #2067f5, #154096); background-image: -ms-linear-gradient(top, #2067f5, #154096); background-image: -o-linear-gradient(top, #2067f5, #154096); background-image: linear-gradient(to bottom, #2067f5, #154096); }
这里我们有一个 div,它将构成一个 100 × 100 像素的盒子。在 CSS 中,div 的背景现在有了一些渐进的增强。每个浏览器都能看懂的第一个后台声明:background-color: #2067f5
。现在,如果您站点的访问者碰巧在能够理解接下来的六个声明之一的浏览器上查看这段代码,他们不仅会看到一个蓝色的框,还会看到一个带有漂亮渐变的框。本质上,每个人都得到了一个蓝盒子,但是一些访问者得到了一个更好的蓝盒子。
有许多工具可以帮助创建各种特定于浏览器的设置。我们用的一个是[
css3please.com](http://css3please.com)
图 2-3 显示了 Chrome 浏览器中 someClass 样式的结果。
***图 2-3。*chrome 中的 someClass 风格示例
正如你所看到的,它从一个中等的蓝色渐变到一个较暗的蓝色。
使用特征检测来驱动渐进增强
在 HTML5 中,渐进式增强从未如此显著。支持 HTML5 的浏览器提供了大量的功能,我们可以用很少的开销来使用,因为它们是浏览器固有的。使用 HTML5,我们不需要向浏览器发送 JavaScript 文件,只需指定新的标记选项和 CSS3,让浏览器为我们做一些有趣的工作。然而,就目前而言,HTML5 的许多强大功能将不得不用旧浏览器上的脚本来完成,这样我们就可以在所有浏览器上获得相同的功能,不管它们是否支持 HTML5。
进入特征检测。通过使用特性检测,我们可以切换到更原生的特性,从而在浏览器中处理性能更好的特性。这是通过混合使用 JavaScript 中的布尔开关和 CSS 来实现的,前者用于检测浏览器支持的功能,后者用于在某个功能不受支持时提供替代实现。浏览器会忽略(而不是抛出错误)它不理解的 CSS 选择器或属性。因此,我们可以放入 CSS3 渐进增强,而 IE8(举例来说)会忽略它们(见清单 2-7 )。
在我们更多地讨论特征检测之前,让我们考虑一个常见的替代方案。许多网站试图检测每个访问者使用的浏览器,并显示针对该浏览器优化的页面。比方说,我们检测到一个使用 IE8 的访问者,并提供一些非 HTML5 的替代功能。虽然这种方法在理论上可行,但实际上却是一个巨大的负担。随着浏览器和版本的激增,维护一个网站的版本很快变得非常昂贵。此外,Chrome 和 Firefox 等浏览器的版本更新速度很快,Chrome 的版本更新是自动的,这增加了更多的开销。因此,将代码绑定到特定版本的浏览器变得更加难以管理。更糟糕的是,尝试这种策略的网站很快就会发现,开发人员除了维护所有这些特定于浏览器的版本之外,什么也不做,而且从不采用可以创造更好的访问者体验并最终创造更有利可图的网站的新技术。前面没有一个解决欺骗用户代理的问题,这会使情况变得更加复杂。
因此,我们强烈建议您使用功能检测,而不是特定于浏览器的网站版本。这样,你就可以发现你需要的功能是否可用,如果可用,就使用它们,如果不可用,就向访问者提供一个有吸引力的替代方案。因为很少有网站使用所有可用的功能,所以您可以只关注您需要的少数功能,这使得代码更易于维护,并确保每个访问者都能看到一个有吸引力的网站。
在撰写本文时,我们认为实现特性检测的最佳方式是使用 Modernizr 开源库。您可以在[
www.modernizr.com/](http://www.modernizr.com/)
找到 Modernizr 项目,并从[
www.modernizr.com/download/](http://www.modernizr.com/download/)
下载
Modernizr 通过使用 JavaScript 测试一个特性是否可用来工作;然后,它将一个类添加到 body 标签中,注明它是可用的还是不可用的(也就是说,添加了类canvas
或no-canvas
)。你也可以用 JavaScript(也就是if(Modernizr.canvas){ do something })
来检查它的可用性。然而,它运行的每一个测试都有性能成本;虽然非常轻微,但每次测试仍然需要时间。因此,Modernizr 的另一个优点是,在下载 Modernizr 脚本文件时,您可以选择想要检测的特性。例如,如果您知道您的网站不使用canvas
元素,您可以取消 canvas 选项。
有关使用 Modernizr 的更多信息,请参考位于[
modernizr.com/docs](http://modernizr.com/docs)
的 Modernizr 文档
拥抱关注点的分离
正如我们在第一章中提到的,要考虑的一种性能是开发人员的性能。如果他们不会让网站访问者的体验变得更糟,你可以做的事情来提高 web 开发人员的性能通常比支付采用新方法所需的时间要多。拥抱关注点分离就是其中之一。熟悉 MVC 的人会经常听到“关注点分离”,但是这个表达的根源可以追溯到 1974 年。 1 如果你以前没听说过这个想法,那就是把功能分成逻辑区域,这样它们就不那么脆弱,也更容易理解。事实上,我们断言,web 开发中的关注点分离不仅会导致代码不那么脆弱、更容易理解,而且还会提高浏览器的性能,因为将样式分离到 CSS 中比使用 HTML 或 JavaScript 来控制外观更快。在前端,HTML、CSS 和 JavaScript 构成了关键的三重奏。
在过去,使用 HTML、CSS 和 JavaScript 的重叠组合作为任何问题的解决方案是很常见的,这种技术被亲切地(或不那么亲切地)称为 DHTML。图 2-4 显示了这种过度交织的关系。
1 Edsger W. Dijkstra,“论科学思想的作用”,Edsger W. Dijkstra,计算文选:个人观点(纽约:斯普林格出版社,1982 年),第 60-66 页。国际标准书号 0-387-90652-5。
***图 2-4。*过度交织的网络开发问题
在 web 开发人员开始接受关注点分离之前,我们使用 HTML 表格作为设计元素,使用 JavaScript 生成 HTML 的大部分,或者做当时看起来有用的任何事情。这种方法在本质上非常实用,只有在前一个月左右编写的页面才是可维护的;否则,我们将无法记住它是如何工作的,并将不得不重新计算代码。
一个更合理、更易维护的方法是让三者中的每一部分都做自己最擅长的事情。虽然这三者必须相互重叠,但是有可能(而且肯定是可取的)让它们以一种使阅读和维护代码更加容易和快速的方式重叠。图 2-5 显示了这三个关注点之间更好的关系。
***图 2-5。*更好地分离关注点
HTML
HTML 是内容的所有者和来源。然后,您可以使用 CSS 和 JavaScript 与该内容进行交互。你会注意到它确实与 CSS 有些重叠;你必须在 HTML 中的不同元素和 CSS 中的规则之间有足够的联系来实现你的设计目标。
CSS
CSS 是表现的大师。出于演示的目的,CSS 提供了最好的性能,尤其是如果你注意使用正确的选择器(我们将在第三章中讨论)。除了 CSS 的现有优势,CSS3 还让您减少对图像的依赖,以帮助呈现圆角、阴影、复杂渐变和其他效果。您还可以在 CSS 中利用 SVG 来创建一些令人惊叹的效果。CSS3 还允许我们创建大量的交互,而无需使用 JavaScript 或其他技术,这些技术通常会处理网站中的菜单弹出或其他动画效果。虽然您仍然需要在不支持 CSS3 的浏览器上使用 JavaScript 来制作动画,但是您可以使用特性检测来仅在必要时使用 JavaScript。如图 2-4 所示,CSS 可以减少对 JavaScript 定义交互的依赖。
JavaScript
JavaScript 是动态数据之王。首先,这是 AJAX 的关键部分。它与 HTML 重叠,因为它可以将 HTML 输入浏览器(通常由数据库交互生成)。JavaScript 以前也是交互之王,现在在争夺用户输入方面有了一个新的伙伴,即 CSS(如前所述)。除了能够卸载大量的鼠标交互功能,包括悬停和点击,CSS 还可以处理动画功能,这曾经是 JavaScript 的专有权限。
让我们考虑一个交互的例子:当访问者悬停在标题或图标上时,让文本出现。图 2-6 显示初始状态(用户将鼠标移动到标题或图标上之前)。
***图 2-6。*交互示例初始状态
当访问者悬停在标题或图标上时,鼠标向下滑动会出现描述,如图图 2-7 所示。
***图 2-7。*交互过程中的交互示例
我们通过在“browserArticle”类上使用pseudo
hover 类来创建这个效果。通常我们会使用 JavaScript,比如 jQuery 的$(".browserArticle";).slideDown()
和$(".browserArticle").slideUp()
,来获得“滑出”效果。但是我们可以使用本地的现代浏览器功能,并调用在“browserArticle”类(transition: all 0.5s ease-in-out;
)中定义的过渡,该类表示如果有任何更改(所有部分),尝试将它们表达为一个过渡(动画),在 0.5 秒内以渐出渐出的方式发生。因此,当我们将悬停设置为高度 45 像素时,它会显示一个不错的过渡。为了与渐进增强保持一致,描述仍然出现在较旧的浏览器上(如 IE 7 和 8),只是没有动画效果。清单 2-8 显示了使动画工作的修改后的 CSS。
***清单 2-8。*滑出动画的 CSS
`.browserArticle
{
position: relative;
width: 200px;
height: 45px;
padding-left: 48px;
overflow:hidden;
-webkit-transition: all 0.5s ease-in-out;
-moz-transition: all 0.5s ease-in-out;
-ms-transition: all 0.5s ease-in-out;
-o-transition: all 0.5s ease-in-out;
transition: all 0.5s ease-in-out;
}
.browserArticle:hover
{
height: 110px;
}
.subTitle
{
font-size: 18px;
margin-top:0;
}
.evilChromeLogo
{
background: url(img/evilChromeLogo.png) no-repeat 0 0;
height: 36px;
width: 38px;
position: absolute;
left: 0;
top: 3px;
}
.accentColor1
{
color: #1C70AD;
}
.description:hover
{
height: 50px;
}`
只要有可能,我们更倾向于将交互功能卸载到 CSS,因为浏览器可以使用它们的本地代码来处理它,从而实现更好的性能。此外,从 CSS 获得相同的功能通常需要比 JavaScript 代码更少的代码。
总结
在这一章中,我们介绍了一些有助于提高性能的信息——对于访问我们网站的人,对于我们自己,对于我们的队友。特别是,我们研究了
- 浏览器如何加载网页。
- 如何使用 CSS 来防止页面的区域互相践踏,并减少跨浏览器的不愉快。
- 如何使用渐进式增强为每一个访问者提供良好的体验。
- 如何使用关注点分离的概念使我们的代码更容易开发和维护。
我们确信,当您使用这些技术时,您会找到自己的方法来进一步完善它们,并使它们与您的工作模型相匹配,就像我们已经做的那样。我们希望,一旦你这样做了,他们将为你提供同样强大的好处,他们已经给了我们。
在下一章中,我们将讨论更多具体的方法来改善页面加载时间(也就是说,从访问者的角度来看性能)。
三、性能指南
我们的经验和研究让我们创建了一套在网站上工作时牢记在心的性能准则。碰巧的是,我们发现我们的指导方针与雅虎、谷歌和其他做同类最佳网络开发的公司的指导方针基本一致。
除了一个例外,我们相信这些规则可以让任何网站变得更好。与内容和流量较少的网站相比,它们对高内容、高流量网站的帮助更大,但即使是个人网站也能从良好的性能中受益。这些准则中的例外是内容交付网络(CDN)的使用。如果你有足够的内容和流量使其在经济上可行,CDN 就有意义,否则就没有意义。
注意本章中没有一条规则是专门针对 HTML5 或 CSS3 或任何其他特定技术的。然而,在一本关于性能的书中,如果不提供这些信息,我们就是失职。
为什么页面加载时间很重要
除了希望为访问他们网站的人提供最好的体验,并希望尽可能地做好工作之外,web 开发人员还有另一个非常好的理由来关注页面加载时间。2010 年 4 月,谷歌开始将页面加载速度作为搜索排名的一个因素。排名不靠前的网页吸引的顾客更少,销售也因此受到影响。WebSiteOptimization.com 综合了多项研究的结果,得出了以下结论:
Google 发现,从 0.4 秒加载 10 个结果的页面到 0.9 秒加载 30 个结果的页面,流量和广告收入减少了 20% (Linden 2006)。当谷歌地图的主页从 100KB 减少到 70-80KB 时,第一周的流量增加了 10%,接下来的三周又增加了 25 %( Farber 2006)。亚马逊的测试显示了类似的结果:Amazon.com 的加载时间每增加 100 毫秒,销售额就会下降 1% (Kohavi and Longbotham 2007)。
WebSiteOptimization.com2
1 来源:[
googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-searchranking.html](http://googlewebmastercentral.blogspot.com/2010/04/using-site-speed-in-web-searchranking.html)
。
2 来源:[
www.websiteoptimization.com/speed/tweak/psychology-web-performance/](http://www.websiteoptimization.com/speed/tweak/psychology-web-performance/)
。
在我们看来,每 100 毫秒销售额下降 1%都是巨大的影响。显然,页面加载时间肯定是 web 开发人员关心的一个关键问题。
指导方针
以下各节描述了一个特定的准则(按照对页面加载时间的影响顺序):
- 减少 HTTP 请求
- 使用内容交付网络(CDN)
- 避免空的
src
或href
属性 - 添加过期标题
- 用 GZIP 压缩部件
- 将 CSS 放在顶部
- 将 JavaScript 放在底部
- 避免 CSS 表达式
- 移除未使用的 CSS
- 缩小 JavaScript 和 CSS
- 最小化重绘
减少 HTTP 请求
减少 HTTP 请求是性能准则星座中的一颗闪亮的星星。这是一个复杂的主题,因此我们将其分为以下几个单独的性能指导:
- 了解并行连接
- 组合资源文件
- 使用图像精灵
了解并行连接
我们注意到,减少 HTTP 请求的想法经常被忽视,尽管这是大多数网站受益的最大的性能提升。开发人员关注的是后端的复杂性,而没有意识到很多加载瓶颈都在浏览器中。因为网站的开发者不能控制浏览器做什么,但是可以控制他们自己的服务器、数据库和代码做什么,他们自然关注他们能控制的。这很好,直到开发人员无法解释浏览器是如何工作的。那就成问题了。图 3-1 说明了这个感知问题。
***图 3-1。*页面负载影响:开发者认知与现实
开发人员经常忽略的浏览器端限制是浏览器一次可以加载多少资源。HTTP 1.1 规范规定,“单用户客户端不应该与任何服务器或代理保持超过 2 个连接。”近年来,大多数浏览器都超过了这个建议。许多浏览器目前支持四个并行连接,少数支持六个。IE8 根据客户端的带宽改变其连接,从拨号连接的两个连接到宽带连接的六个连接。
没有什么比插图更能说明问题了,所以让我们考虑一个例子。图 3-2 显示了apple.com
(通过[
www.webpagetest.org/](http://www.webpagetest.org/)
)的加载。
**图 3-2。**为 Apple.com加载资源
请注意,HTML 的加载时间是 327 毫秒。也就是说,一个页面的文字内容下来的速度非常快。请注意,其他文件(大部分是来自 images.apple.com 的图像)是成批到达的。这种分组模式是浏览器可以打开的并行连接数量的直接结果。有点像铁路编组站。在一个繁忙的车场,有很多火车,但只有几条出站轨道。因此,调度员不得不分组发送列车,而不是一次发送所有列车。大型网站也有同样的问题。
你可以将资源放入多个主机(比如[www.apple.com](http://www.apple.com)
和 i mages.apple.com
,在图 3-2 所示的例子中。)然而,这种做法只能在一定程度上提高性能,因为额外的 DNS 查找成本会导致回报迅速减少。
组合资源文件
并行连接问题的结果是文件越大越好。我们知道对于一些开发者来说这听起来像是疯狂的异端邪说,但这是真的。很长一段时间,我们努力使资源变得更小。我们还记得 1200 波特调制解调器和拨号连接的时代,那时看图像进来就像网页的进度条。然而,时代变了,绝大多数人都有快速的互联网连接。有了我们现代的基础设施,任何给定的文件都不可能扼杀浏览器。因此,大文件越少越好。回到我们的编组站的例子,如果我们能在每列火车上放更多的车,我们就能在同样的轨道上得到更多的货物。(顺便说一句,长期以来,铁路公司一直在寻求每辆列车装载更多的汽车。在他的其他爱好中,杰伊是一位铁路历史学家。)这同样适用于文件和并行连接。
此外,每个 HTTP 请求在时间和带宽上都至少有一些开销。因此,如果您能够组合您的资源,使得您需要更少的 HTTP 请求来呈现一个页面,您将为站点的访问者获得更快的呈现速度。
所有这些考虑的总和就是你应该把你的内容合并到更少的文件中。如果可能的话,将多个 CSS 样式表合并成一个文件,将多个 JS 文件合并成一个文件。当不同的页面使用不同的 CSS 和 JS 文件时,将它们组合起来,使每个页面都获得一个 CSS 文件和一个 JS 文件,这可能是一个问题。但是,您可以通过使用每次修改 CSS 和 JS 文件时运行的构建脚本来解决这个问题。这样的构建脚本将确定每个页面使用的文件,创建所需的唯一文件,并向每个页面添加所需的链接元素。如果动态页面共享一组公共的 CSS 和 JS 文件,动态内容仍然可以从这样的构建系统中受益。
另一个策略是在交付页面时动态地组合 CSS 和 JS 文件。考虑到这一步需要处理,它不会像使用预构建文件那样快。但是,如果你的网站足够复杂,它可能仍然是比使用许多单独的文件更好的选择。
最后,另一个策略是提交一个对所有页面通用的 CSS 文件,然后,当访问者点击每个页面时,提交另一个特定于该页面的 CSS 文件。考虑到页眉和页脚以及其他大的区域通常在不同的页面上保持不变,这种策略可以使维护变得更容易,并且仍然可以为提供页面的公司和网站的访问者带来很多好处。该公司节省了一遍又一遍地传递普通 CSS 文件内容的带宽和其他开销,访问者获得了更好的性能。对于非常大的站点,这种最终的策略通常代表了可维护性和性能之间的一种很好的折衷。
使用图像精灵
图像精灵实际上只是组合的图像文件。它们提供了一种便捷的方式来实现将小文件合并成大文件的目标,并通过浏览器相对较少的并行连接更快地传递内容。
大多数网站在网站页面上使用图像集合。减少 HTTP 请求的一种方法是将所有这些常见的图像放在一个图像中(一个图像精灵)。然后,每当您需要这些图像中的一个时,您就引用 sprite 并在 sprite 中指定一组坐标。因此,将所有的徽标、自定义项目符号、导航提示和其他常见的图像宣传材料放在一个图像中,并在所有页面中使用该图像。
提高性能的一个有趣的技巧是按颜色范围对精灵进行分组,然后保存每个精灵文件,使其只使用该范围内的颜色。这种技术使每个精灵文件变得更小。这对于使用受限调色板的网站尤其有效。例如,如果你的公司的标志是红色和灰色的,公司的营销人员可能已经创建了一堆使用公司配色方案的图像,用于导航和其他目的。在这种情况下,您可以通过为这些图像创建一个文件并将颜色范围限制为红色和灰色来节省大量空间。
如果你有许多共同的图像资料,你可能需要一个以上的图像精灵,即使你不按颜色范围分开。此外,如果您公司的不同部门维护不同部分的公共图像资料(可能一个小组维护导航图像,而另一个小组维护徽标),您可能希望有单独的文件。
当我们写这一章的时候,一个问题出现了(来自我们的技术评论者,Jeff Johnson ),什么时候一个 sprite 文件大到应该被分割?这是一个很好的问题,因为在一定程度上,一个大文件比两个小文件更成问题。但是,这一点会因多种因素而异,例如客户端需要多少其他 HTTP 请求才能完全加载您的站点,站点是否使用 CDN,甚至访问者使用的浏览器。因此,我们无法给出一个确切的答案。我们只能告诉你,如果你认为把大的 sprite 文件分割成小的 sprite 文件或者把小的文件合并成大的文件可能有利于你的页面加载时间,建立一些度量标准和一种方法来监控这些度量标准,然后尝试它们。正如 web 开发中的许多其他事情一样,没有单一的最佳方式。通常,我们必须通过测试来找到适合特定情况的答案。
不过,作为一条经验法则和一个好的起点,抑制 HTTP 请求比其他问题更重要。因此,如果可以的话,将所有常用的图片合并成几个图片。理想情况下,将它们放在一张图片中。然后使用偏移来显示图像的右边部分。图 3-3 显示了来自google.com
的精灵。
图 3-3。【Google.com 雪碧
为了使用这个 sprite,我们创建了一个使用类的 div,然后定义了匹配的 CSS 类,它指定了我们想要的图像的细节。清单 3-1 显示了 div 元素。
**清单 3-1。**一个div
来自一个精灵的图像
<div class="arrowPrev"></div>
清单 3-2 展示了 CSS 类。
***清单 3-2。*一个精灵的 CSS
. arrowPrev { width: 22px; height: 25px; background-image: url(googlesprite.png); background-position: -6px -13px; background-repeat: no-repeat; }
CSS 指定精灵、精灵中包含图像的视口的宽度和高度,以及图像的起点(实际偏移量)。它还指定图像不应重复。
结果就是上一个箭头,如图图 3-4 所示。
***图 3-4。*使用精灵的结果
sprite 的另一个好处是,只从 sprite 中加载一个图像就可以将整个 sprite 放入浏览器的缓存中。因为 sprite 的每次后续使用都不需要获取图像,所以可以节省许多 HTTP 连接。在谷歌的例子中,这个有 60 个小图片的 sprite 可以节省多达 60 个 HTTP 连接。这是一个巨大的性能增益。
您可以找到许多不同的网站来帮助您使用 sprite 文件。过去,我们使用过[
www.spritecow.com](http://www.spritecow.com)
和[
www.spritebox.net](http://www.spritebox.net)
使用内容交付网络(CDN)
一个内容交付网络有许多战略性放置的服务器,以创建一个覆盖全球的 web。因此,在奥斯汀或巴黎访问你的网站的人有一个很短的跳转到它的素材。问题是这些文件不容易更改,所以您应该只将它们用于更改不多的资源,如图像、字体、JavaScript 库、媒体等。将所有静态内容放在用户附近确实可以提高性能。相反,必须是动态的内容通常应该从单个位置提供。即使对于大公司来说,跨地理上分离的服务器同步数据库事务所需的努力也很少是值得的。光是时间问题就经常让网络工程师们抓狂。因此,大多数网络企业应该把购物、登录和其他依赖数据的交易放在一个地方。
使用 CDN 的技巧之一是在文件前添加时间戳。这样你就有了一个独一无二的文件,而不必担心一个过时的文件被缓存在 CDN 服务器上分发给你的访问者。当你用一个新的时间戳更新文件时,你也必须更新你的引用代码。如果你在中小型网站上工作,这似乎是一个麻烦,所以你必须判断 CDN 是否适合你的项目。这确实增加了成本,而且如果你的网站支持一个本地企业或者在地理上是孤立的,这就没有意义了。
**注:**如果一个内容交付网络不能帮助你,那么它就不是真正的次优性能提示。但是,我们把它留在这里,因为如果您确实需要,CDN 在提高页面加载性能方面仅次于降低 HTTP 请求的数量。
避免空的 src 或 href 属性
我们看到的模式是创建一个具有空属性的img
元素,然后在用 JavaScript 加载页面的过程中动态分配属性src
的值。这样做的问题是,元素总是在脚本运行之前被评估(特别是如果你把脚本放在其他所有东西之后,就像我们在本章后面推荐的那样)。因此,浏览器会尝试评估该空属性,并创建一个 HTTP 请求来执行此操作。
类似的模式和问题也出现在href
属性中,通常在锚元素中。有时,开发人员希望使用锚元素作为基于 JavaScript 的交互的触发器。问题是,如果 href 属性为空,当用户触发交互时,浏览器会向服务器发送一个 HTTP 请求。这不会影响页面加载时间,但会在服务器上产生不必要的流量,浪费带宽,并可能降低所有访问者的交付速度。解决这个问题的简单方法是将 href 属性的值设置为一个不执行任何操作的 JavaScript 命令。清单 3-3 展示了一个修复的例子。
***清单 3-3。*修复一个空的href
属性
<a href="javascript:;" class="triggerName">Trigger</a>
然而,仅仅使用空的 JavaScript 命令并不是最好的解决方案。更好的方法是提供一个描述(当用户将鼠标悬停在链接上时,它会出现在状态栏中)并阻止对href
进行评估。清单 3-4 展示了如何去做。
***清单 3-4。*创建一个描述性的href
属性
`Trigger
`现在,网站的访问者在承诺做某件事之前会得到一个提示,href 不会创建一个浪费的 HTTP 请求。
我们应该指出的是,当我们为关闭或无法访问 JavaScript 的访问者提供单独的演示时,我们解决这个问题的方法有所不同。在这些情况下,我们使用实际的链接。
我们还应该指出,空的 src 和 href 属性也会导致错误。如果您跟踪请求头中的状态(无论是通过 cookies 还是其他机制)并发送一个空属性,您可能会丢失对状态的跟踪。在这一点上,你有一个很好的机会让访问者感到沮丧,他们会很快把他们的业务转移到其他地方,或者如果他们别无选择只能使用你的网站,他们会非常生气。
当然,您也想在每次捕获空属性时写入日志文件。如果你有很多这样的问题,你肯定想找出为什么会这样,并解决它。
添加过期标题
您应该为所有静态组件(图像、样式表、脚本、flash、PDF 等)添加一个Expires
头。添加一个日期在未来很久的Expires
头可以让浏览器缓存您的静态内容。清单 3-5 显示了一个典型的 Expires 报头。
***清单 3-5。*典型的过期报头
Expires: Wed, 1 Jan 2020 00:00:00 GMT
因此,当这些访问者返回时,他们的浏览器将不必为后续访问获取静态内容,并且他们将有更快的加载时间。当然,添加Expires
标题对于第一次访问或者在两次访问之间清空缓存的人来说没有任何作用。另一方面,这不会对他们造成任何伤害,至少对一些游客有益。事实上,由于人们倾向于一遍又一遍地访问同一个网站,这可能会改善大多数访问者的体验。
在很久以后设置Expires
头文件的缺点是,您必须重命名设置了Expires
头文件的文件。回头客会缓存你的素材,你希望他们有你更新的素材。因此,您将需要某种版本控制方案。一个有趣的方法是在文件名中加入一个日期戳。例如,您的基本样式表可能被命名为base20120303.css
。向文件添加时间戳的有趣之处在于,您可以立即看到版本控制系统中的变更历史。如果您觉得添加日期戳会使文件名太长,您可以使用一个简单的版本号来表示文件被修改的次数。例如,如果您已经修改了您的基本样式表 13 次,它可能被命名为base13.css
。
关于如何设置Expires
头的一个很好的例子,请看来自[
www.html5boilerplate.com](http://www.html5boilerplate.com)
的htaccess
文件(我们在第六章的中也使用了它)。
用 GZIP 压缩组件
HTTP/1.1 规范引入了Accept-Encoding
头,可以表示 HTTP 请求中的内容是压缩的。这样的标题出现在清单 3-6 中。
***清单 3-6。*一个接受编码报头
Accept-Encoding: gzip, deflate
如您所见,该标头指定了两种压缩方式。GZIP 更常见,因为它是可用的最有效的压缩方案。根据雅虎的“加速你的网站的最佳实践”页面,GZIP 减少了大约 70%的回复大小。英特尔的一名工程师进行的一项研究显示,对于某些文件类型(文本得分最高),节省高达 90%,但雅虎的 70%可能是所有文件类型的平均水平。
压缩减少了获取压缩资源所需的时间,从而改善了访问者的体验。它还减少了带宽,这为提供页面服务的公司节省了资金(如果访问者没有从他们的 ISP 或移动运营商那里购买无限带宽,也可能会节省资金)。
3 来源:[
developer.yahoo.com/performance/rules.html#gzip](http://developer.yahoo.com/performance/rules.html#gzip)
。
4 来源:[
software.intel.com/en-us/articles/http-compression-for-web-applications/](http://software.intel.com/en-us/articles/http-compression-for-web-applications/)
。
压缩的一个问题是仍然有一些浏览器(更罕见的是代理)处理不当。因此,您需要在标题中添加一个Vary
字段,以便这些浏览器和代理可以协商未压缩的内容。将Vary
字段添加到标题中是通过标题中的指令来完成的,如清单 3-7 中的所示。
***清单 3-7。*向标题添加一个Vary
字段
Header set Vary *
**注意:**根据网络服务器的不同,你如何设置标题和其中的字段会有很大的不同。我们展示的是需要出现在 HTTP 头中的输出,而不是用于设置它的任何特定代码集。
你应该压缩任何本质上是文本的内容。这意味着你应该压缩你的 HTML,CSS,脚本,XML,JSON,以及其他任何实际上只是文本的东西。图像和 PDF 文件不应该被压缩,因为压缩应该是它们存储格式的一部分。如果有人制作了一个未压缩的图像或 PDF 文件(这是可能的,至少对于 PDF 来说),补救措施是修复文件,而不是压缩这种内容。
您不想压缩图像和 PDF 文件的原因是,当您压缩它们时,它们实际上会变得更大。压缩引擎实际上不能使资源变小,但是因为它仍然必须添加自己的控制代码,所以文件变大了。因此,你不应该压缩所有的东西。
将 CSS 放在顶部
如果您的页面包含样式信息,将该信息放在顶部(在head
元素中)。为了避免重绘,许多 web 浏览器在获得所有样式信息之前不会开始呈现页面。因此,如果您的样式信息在页面的底部,这些浏览器在开始呈现任何内容之前都会加载所有内容。你可怜的网站访问者坐在那里看着一个白色的屏幕很长一段时间,因此,许多人会找到其他地方访问。
大型网站和缓慢的连接加剧了这一问题。一个页面的内容越多,风格信息就越有必要放在内容之前。仍然有一些人使用拨号上网,我们应该尽我们所能让他们的网络体验尽可能愉快。此外,许多人(包括本书的作者)在移动设备上进行相当多的网上冲浪,许多地方的移动连接性能仍然相对较慢。我们当然不想失去移动访问者的业务,所以我们需要让他们至少看到一些内容,而其余的内容加载。因此,我们想把 CSS 放在页面的顶部。
有趣的是,许多浏览器在呈现任何内容之前都会加载所有的样式信息,这一事实也支持将页面的外部 CSS 合并到一个文件中。通过网络获取多个文件自然比获取单个文件慢。因为我们希望用户尽可能快地看到一些东西,所以理想情况下,我们只希望一次获取样式内容。
将 JavaScript 放在底部
脚本阻止并行下载。换句话说,当浏览器下载一个脚本时,它并没有下载任何其他东西。如果您的脚本在页面的顶部,那么您就阻止了在页面的其余部分加载时向用户显示页面的一部分。
您可以在script
元素中使用DEFER
属性,让浏览器知道它可以在下载这个脚本的同时下载其他内容。然而,这样做有两个问题。首先,并不是所有的浏览器都支持DEFER
属性。第二个是使用DEFER
属性的契约是任何具有该属性的脚本都不使用document.write
。因此,您不能在使用document.write
的脚本中使用DEFER
属性。(在本章的后面,当我们讨论为什么重新排列 DOM 不是一个好主意时,我们将看到为什么避免document.write
是一个好主意。)
通过将所有脚本放在末尾(就在body
元素的结束标记之前),您实际上已经将脚本加载推迟到了末尾,从而轻松地避免了并行下载受阻的问题。此外,您不必依赖就绪事件来确保元素可用,因为所有的元素都将在任何脚本运行之前准备好。
避免 CSS 表达式
Internet Explorer 支持版本 5、6 和 7 的 CSS 表达式。其他浏览器从不支持它们。
CSS 表达式允许在页面加载时动态设置样式。清单 3-8 显示了一个 CSS 表达式(来自微软的动态属性网页)。
***清单 3-8。*一个 CSS 表情
object.style.left=(document.body.clientWidth/2) - (object.offsetWidth/2);
该表达式试图将一个元素居中。你可以用清单 3-9 中显示的 CSS 达到同样的效果。
***清单 3-9。*一个 CSS 表达式的替换
.center { margin-left: auto; margin-right: auto; width: 200px; }
当缩小时(我们将在本章后面讲到),常规 CSS 比 CSS 表达式短,所以这个特殊的 CSS 表达式没有什么意义。
CSS 表达式的缺点是它们通常会比作者预期的更频繁地被求值。理想情况下,只有在呈现页面时(包括页面刷新时)才会对它们进行评估。然而,当用户上下滚动页面或者只是移动鼠标时,它们通常会被重新评估。许多用户(我们听过 80%的数字)“用鼠标看”,这意味着无论他们的眼睛看向哪里,他们的鼠标都会跟着看。想象一下,如果有人在读一篇文章,鼠标会移动多少。因为 CSS 表达式在鼠标移动时会被重新计算,所以当页面在浏览器中时,表达式可能会被计算数千次(我们已经看到了数以万计的引用)。这真的会扼杀可怜的网站访问者的性能。
移除未使用的 CSS
在大多数浏览器中(据我们所知,实际上是所有的浏览器),浏览器的样式引擎会评估 CSS 规则,以找到每个元素的匹配。在这样做的时候,它必须通过所有的 CSS 规则。因此,如果一个样式表有任何未使用的规则,就会给样式引擎带来更多的工作,却没有任何收获。移除未使用的规则还会使 CSS 文件变小,这允许浏览器更快地获取它并节省带宽。
5 陈明志、安德森和孙明辉,“鼠标光标能告诉我们更多什么?网络浏览中眼睛/鼠标运动的相关性”,《人机交互会议录(CHI)】(2001):280–81。
为整个网站制作一个单一的样式表,甚至在一些页面没有使用样式表中的所有规则时也使用它,这很有诱惑力。然而,这样做通常是错误的,因为如果不需要的规则不存在的话,这些页面不会加载得那么快。
在这个问题的现有解决方案中,我们最喜欢的是制作一个包含所有页面通用的规则集的样式表,然后为站点上的每个区域(甚至页面)创建其他样式表。例如,所有页面可能包含一个名为all.css
或base.css
的样式表,而与购买产品相关的页面可能包含一个名为product.css
或buy.css
的附加样式表。
另一个解决方案是为站点页面使用的 CSS 规则的每个唯一组合制作一个单独的样式表。根据您的开发环境,您可以使用构建或其他服务器端脚本来创建这些文件。鉴于大多数大型网站都是动态创建页面的,创建页面的系统需要逻辑来决定使用哪种样式表。
还有一种解决方案是在请求页面时动态构建样式表。这样做的缺点是服务器端需要额外的处理,并且需要生成相同的文件名,以便浏览器可以缓存样式表。
最后,开发人员可以为 CSS 规则的每个独特组合手工制作一个样式表,并记住何时使用每一个。然而,任何依赖于人的记忆的东西都是如此不可靠,以至于我们宁愿为每个页面使用相同的样式表来降低性能。
缩小 JavaScript 和 CSS
“缩小”就是从源代码中删除所有无用的字符。对于 JavaScript 和 CSS,您应该删除所有不会破坏代码的空白(包括换行符)、不会破坏代码的所有块分隔符以及所有注释。换句话说,无情地删除任何不一定要出现的字符,以使代码工作。精简代码有助于加快加载速度和降低带宽使用率。
我们更喜欢使用缩小工具来缩小自己。人类容易出错,要么把不该去掉的东西去掉,要么把能去掉的东西留在里面。此外,通过使用工具,我们可以像我们喜欢的那样详细,包括对我们的开发伙伴的注释,并使源文件易于阅读,并且仍然相信我们会因为我们的缩小工具而获得良好的性能。
缩小工具的最后一个好处(这是大多数缩小工具的可选设置)是能够用非常短的名字替换所有的变量名,或者用短的名字替换长的东西,这是另一个节省文件大小的方法。缺点是内容很难理解——如果在这种状态下对其进行了更改,则更容易出错——在这种替换之后。不过,如果您将源文件作为冗长文件进行维护,然后只在每个版本中或者只在提交给站点访问者时进行缩减,这也没什么问题。
我们使用雅虎的 YUI 压缩器来压缩 CSS 和 JavaScript。你可以在 http://developer.yahoo.com/yui/compressor/买到。
为了提供一个合适的例子,我们在清单 3-10 中重复了清单 2-2 。
***清单 3-10。*一个“健谈”CSS 的例子
.browserArticle { /* We set the position: relative so the absolutely positioned element within uses this box to position itself */ position: relative; width: 200px; padding-left: 48px;
` /* We set a minimum height in case there’s not enough content
to make the box big enough to house the image. We use the
height of the image plus 3 for the top offset. */
min-height: 39px;
}
.subTitle
{
font-size: 18px;
}
.evilChromeLogo
{
background: url(img/evilChromeLogo.png) no-repeat 0 0;
height: 36px;
width: 38px;
position: absolute;
left: 0;
top: 3px;
z-index: 1;
}
.accentColor1
{
color: #1C70AD;
}`
清单 3-11 显示了缩小后的同一个 CSS 片段。
***清单 3-11。*缩小 CSS 的一个例子
.browserArticle{position:relative;width:200px;padding-left:48px;min-height:39px}.subTitle{font- size:18px}.evilChromeLogo{background:url(img/evilChromeLogo.png) no-repeat 0 0;height:36px;wi dth:38px;position:absolute;left:0;top:3px;z-index:1}.accentColor1{color:#1C70AD}
清单 3-10 有 678 字节。清单 3-11 有 341 个字节。这相当于节省了 337 字节,也就是大约一半。这个例子可能是一个极端的例子,因为在原文中有大量的注释。然而,缩小仍然是一个好主意,即使节省并不明显。
我们要感谢[
freeformatter.com](http://freeformatter.com)
的人们创建了 YUI 压缩器的在线实现。这对于快速检查某物是如何压缩的很方便,我们用它来制作清单 3-11 中的样本。
最小化重绘
我们发现页面重绘非常烦人,我们认为大多数人也是这样。你有没有试过点击一个链接,却让浏览器选择那个时候重新绘制页面,然后发现你什么也没点击,或者更糟的是,点击了错误的链接?这很烦人,对于 web 开发人员来说,避免这种情况是件好事。这里有一套指导方针,可以最大限度地减少浏览器重新绘制页面的次数,以便正确地呈现页面:
- 指定图像的尺寸
- 表格仅用于表格内容
- 指定一个字符集
- 不要重新排列 DOM
指定图像的尺寸
您应该为img
元素指定尺寸。当浏览器创建区域树时,它会为每个元素留出一个区域。如果您不指定一个img
元素的尺寸,浏览器很可能一开始就猜错了,然后在下载图像后纠正错误。当它纠正最初的猜测时,它必须重新绘制页面以正确放置元素。您可以通过提供尺寸来避免重新绘制。
仅对表格内容使用表格
将表格仅用于本质上是表格的内容(而不是作为布局设备)的许多其他原因之一是,当浏览器呈现表格时,表格通常会强制重绘。当浏览器接收到每一行时,它们通常会尝试逐步布局表格。当包含要求不同列宽或行高的内容的行出现时,必须重新绘制前面的行。如果站点开发人员只对表格内容使用表格元素,这通常不会太麻烦。然而,当开发人员使用表格时(尤其是表格中的表格,就像在许多编码很差的站点中一样),当页面加载时,页面的整个部分可以从一个地方“跳到”另一个地方。这对网站的访问者来说是非常不和谐的。
指定一个字符集
大多数浏览器(但不是 IE6、7 和 8)会缓冲页面的一部分,直到找到字符集定义。他们这样做是因为字符集是呈现页面的一个重要因素。不同的字符集可能意味着与浏览器使用默认字符集呈现的外观完全不同。因此,通过将字符集指定为 HTML 中head
元素的第一个子元素,您可以让大多数浏览器更快地向站点的访问者显示内容——比不显示要快得多。
唯一比不指定字符集更糟糕的是,在 HTML 中指定字符集太晚了,以至于浏览器在开始使用默认字符集呈现后才找到字符集定义。在这种情况下,除非开发人员足够幸运地指定了与浏览器默认字符集相同的字符集,否则浏览器会丢弃当前呈现的内容并开始重新绘制页面。
不要重新排列 DOM
重新排列 DOM 通常会迫使浏览器重新绘制页面。通常,浏览器很快就能完成 DOM,因为加载 HTML 文件是浏览器做的第一件事(尽管它可能同时加载 HTML 文件中指定的其他资源)。因此,任何向 DOM 添加元素或从中删除元素的脚本都可能导致浏览器至少重绘部分页面。此外,在 DOM 中移动一个元素实际上相当于从一个位置移除它,然后将它添加到另一个位置,这甚至比仅仅添加或移除元素还要糟糕。
如果由于某种原因必须重新排列 DOM,当有一组节点要插入时,避免一次插入一个节点。例如,如果您希望插入一个列表,不要添加列表(ul
或ol
)元素,然后添加它的每个子元素(li
)。每次插入都会强制重绘,所以添加一个包含两个条目的列表会强制进行三次重绘操作。相反,创建一个包含要插入列表的 HTML 的字符串,然后一次性插入该字符串。这样,您只需要强制执行一次重绘操作。
类似的原则也适用于动态设置元素的样式。不要在 JavaScript 中设置每个样式元素。相反,创建一个包含所有必要样式信息的类,然后在元素上设置该类。同样,您得到一个重绘操作,而不是多个重绘操作。
您确实应该避免修改 DOM。然而,如果你必须这样做(我们也被迫这样做,所以我们知道它会发生),以这样一种方式做,你不是重复地强迫重绘操作。
延伸阅读
在众多关于 HTML 和 CSS 性能优化的信息来源中,我们发现以下是最有用的:
developer.yahoo.com/performance/rules.html
code.google.com/speed/page-speed/docs/rendering.html
总结
这一章介绍了一些优化网站访问者页面加载时间的方法。以下是你可以快速做出的简单改变(如果你还没有的话):
- 将 CSS 放在
head
元素中。 - 将 JavaScript 放在
body
元素下面。 - 指定图像的尺寸。
- 指定一个字符集。
当然,如果你有很多页面,仅仅因为重复,即使简单的修改也会很麻烦。
如果你有能力做出更大、更系统的改变,尝试下面的改进(如果你还没有做的话):
- 通过以下方式减少 HTTP 请求
- 组合您的资源文件。
- 使用图像精灵。
- 避免空的
src
或href
属性。 - 用 GZIP 压缩部件。
- 避免 CSS 表达式。
- 使用高效的 CSS 选择器。
- 使 JavaScript 和 CSS 成为外部的。
- 缩小 JavaScript 和 CSS。
- 尽量减少重画。
最后,我们强烈建议在考虑浏览器如何加载页面的同时,定期检查网页。没有人能一直记住这一点,因为作为开发人员,我们总是会遇到一些奇怪的小问题,需要我们全神贯注。因此,每隔一段时间停下来检查一下页面负载是个好主意。如果你是团队的一员,传递指导方针,让整个团队都参与进来。
四、响应式网页设计
为了与我们指出增强 web 开发人员性能的技术的趋势保持一致,可能对开发人员来说最节省时间的方法之一是“一个代码库”的想法。本质上,它是能够使用相同的代码向桌面浏览器、平板电脑和移动设备提供体验的想法。
传统上,如果你想拥有一个网站的移动表示,你需要创建一个单独的网站,该网站根据目标设备的外形和交互模型进行定制。一般来说,这些独立的网站在代码结构上会有很大的不同,如果你为一家大公司工作,可能会由在移动领域工作的专业开发人员创建和维护。
然后,在 2008 年,W3 为 CSS3 媒体查询创建了一个规范。各种浏览器或多或少地实现了对媒体查询的支持,现在我们可以选择让我们的网站适应访问者的显示形式,而无需服务器逻辑、重定向或复杂的 JavaScript。媒体查询是一系列技术中的一种,这些技术共同创造了响应式网页设计。
在我们走得更远之前,我们需要在该表扬的地方给予表扬。Ethan Marcotte 在一篇名为 List Apart 的文章中创造了“响应式网页设计”这个术语。你可以在 http://www.alistapart.com/articles/responsive-web-design/找到那篇文章。
响应式网页设计
我们可以让我们的网站适应他们被浏览的设备,而不是让不同设备的访问者去不同的网站。一个显而易见的激励因素是开发时间,而不是为每个设备创建一个单独的站点。但是,让我们也考虑一下消费我们网站的设备的快速变化的情况。无论我们是在谈论新型号的平板电脑、智能手机或网络电视的屏幕尺寸,还是谈论全新的外形,如汽车界面,如果我们试图不断调整每个新设备的代码库,我们就会陷入无休止的追赶游戏中。要想看到令人惊叹的各种屏幕尺寸,请访问 http://sender 11 . typepad . com/sender 11/2008/04/mobile-screen-s . html,看看 Morten Hjerde 的作品。从 2005 年到 2008 年,他收集了 400 多种设备的统计数据。想象一下从那时起又有多少人被引进。多亏了这么多的设备,我们发现按屏幕大小而不是按设备(甚至按设备类型)来改变布局要容易得多。
那么响应式设计是什么样子的呢?一个很好的例子请看[
www.bostonglobe.com](http://www.bostonglobe.com)
。在桌面浏览器中打开波士顿环球报的网站,并缩小浏览器的宽度。您将看到内容调整大小以适应新的浏览器尺寸。随着浏览器变得越来越小,你会看到平板电脑会看到的东西,最终会看到智能手机会看到的东西。
这种方法的核心是 CSS3 的媒体查询。然而,通过添加调整图像大小的技术和灵活网格的概念,您可以真正增强您的单代码方法。本章的其余部分将研究如何创建一个响应式网页。
CSS3 媒体查询
W3C 说,“媒体查询由一个媒体类型和零个或多个检查特定媒体特性条件的表达式组成。”。虽然这很有启发性,但让我们更深入一点。
CSS3 可以告诉你浏览器窗口的屏幕宽度。您不必运行任何 JavaScript 或进行服务器端检测。更好的是,CSS 实时响应浏览器宽度的变化。因此,如果你的访问者调整浏览器的大小,CSS 会自动调整。
换句话说,您可以根据浏览器的宽度制定不同的 CSS 规则。清单 4-1 显示了一个例子。
***清单 4-1。*包含媒体查询的样式元素
`
}
/* Tablets and small desktop browsers */
@media only screen and (min-width: 768px) and (max-width: 991px)
{
body
{
width: 700px;
}
}
`
在清单 4-1 中,我们有一个默认的 960 像素的主体宽度。但是,如果浏览器窗口宽度在 768 和 991 像素之间,我们将使用新媒体查询定义的 700 像素宽度来覆盖默认的正文规则。
这种能力提供了一个非常强大的钩子,您可以使用它来定制您的演示文稿——每个人都知道 CSS 是演示文稿之王——以适应任何屏幕尺寸范围。这种技术不仅可以设计内容的样式,还可以让您使用它来选择要显示的内容部分。例如,您可能在桌面站点的右栏中有第三级信息,这些信息虽然信息丰富,但对于使用平板电脑或较小移动设备的访问者来说可能并不重要。因此,您可以为这些大小的第三级内容向规则集添加一个display:none
属性,而不用担心它会脱离屏幕或导致不受欢迎的滚动条。
此外,您可以选择向移动访问者显示不同的导航方案,这种方案更接近于本地应用导航。将桌面网站转换为移动格式时,一个常见的疏忽是忘记用户如何握持设备。因此,底部的导航允许用户使用拇指来导航。虽然将导航方案放在底部并不总是可行的,但一个可行的替代方法是在顶部使用一个列表。无论你选择顶部导航还是底部导航,你都需要避免使用侧边导航,因为这样可以减少水平空间,也可以减少错误的拇指点击。(顺便说一句,你值得信赖的作者都有一双大手,所以他们确实注意到了这种问题。)
让我们看一个稍微复杂一点的例子。以下是四个设计意图,以使我们的人造网站适合桌面浏览器,平板浏览器和移动浏览器在高和宽的方向。
***图 4-1。*我们在桌面浏览器上的山寨网站
***图 4-2。*我们平板电脑上的山寨网站
***图 4-3。*我们的山寨网站上横放着一部手机
***图 4-4。*我们的山寨网站竖着放在手机上
现在让我们看看媒体查询是如何动态地将下面的 HTML 变成四个设计意图的。
**注意:**如果要测试移动高大上的意图,一定要使用 Firefox 以外的浏览器。它不会触发桌面浏览器中最小的视窗。
HTML
清单 4-2 显示了我们的虚拟网站的 HTML 源代码。
***清单 4-2。*我们山寨网站的 HTML】
`
` `Don't Say I'm Not Responsive
Kogi food truck craft beer sriracha vegan raw denim. DIY brunch put a bird on it shoreditch, chillwave art party occupy pork belly. Pop-up forage master cleanse mlkshk. Blog narwhal butcher, american apparel cardigan beard wayfarers bicycle rights typewriter dreamcatcher letterpress. Ethnic odd future cliche, forage american apparel flexitarian pinterest bespoke mixtape. Marfa PBR farm-to-table, wolf typewriter mustache polaroid leggings four loko mlkshk small batch chillwave bicycle rights portland. Salvia polaroid leggings photo booth 3 wolf moon stumptown forage.
` ` Chambray organic art party seitan, post-ironic squid authentic quinoa echo park twee wolf fap readymade fingerstache iphone. Brunch 8-bit put a bird on it butcher +1 beard cray, sriracha cardigan chambray sustainable DIY. Polaroid organic seitan thundercats pour-over truffaut DIY kogi pop-up lo-fi.