HTML5-性能高级教程-一-

81 阅读1小时+

HTML5 性能高级教程(一)

原文: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 显示了我们的样本现场。

images

***图 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)到你的访问者在浏览器中看到的最终呈现版本的过程。

images

***图 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】

`   .dblClick {     width: 200px;     background: red;     color: white;   } I'm an Example

`

最后,您应该将 CSS(包括对外部样式表的引用)放在 head 元素中,并将脚本放在 body 元素的底部。因为浏览器可以在完全解析 HTML 之前开始呈现元素,所以将 CSS 放在头部可以确保这些元素的样式正确。对性能来说更重要的是,您不希望元素必须被重绘,因为您在元素呈现后放置了一个样式声明。此外,无意中看到物品移动也是一种不好的视觉效果。此外,因为浏览器必须评估 JavaScript 文件,将它们放在 HTML 的开头会延迟视觉元素的呈现,并给访问者一种页面加载较慢的感觉。

我们将在下一章讨论将 CSS 放在顶部,将 JavaScript 放在底部。我们还将在下一章讨论一些避免重绘事件的其他方法。

使用 CSS 来管理边界

正如我们将在本章后面的“都是盒子”一节中详细讨论的那样,浏览器将网页呈现为一系列的盒子,而这些盒子通常包含其他的盒子。因此,我们可以说浏览器的自然呈现模型是盒中盒。知道了这一点,明智的做法是安排你的布局与浏览器实现的框中框呈现模型一起工作,而不是与之对抗。

为了充分利用这种框中框的实现,最好的办法是安排每个元素或元素组,使其完全包含在一个框中。相反,糟糕的事情是有东西从你的盒子里伸出来。我们将用一个例子来说明好的和坏的做法。

首先,让我们定义我们在做什么。我们希望创建一个文章堆栈,在文章文本的左侧包含一个图像,在文章文本的上方包含一个标题,并让文章文本垂直延伸到任意高度。我们称之为“堆栈”,因为它在页面上堆叠元素。图 2-2 显示了期望输出的示例。

images

***图 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 样式的结果。

images

***图 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 标签中,注明它是可用的还是不可用的(也就是说,添加了类canvasno-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。

images

***图 2-4。*过度交织的网络开发问题

在 web 开发人员开始接受关注点分离之前,我们使用 HTML 表格作为设计元素,使用 JavaScript 生成 HTML 的大部分,或者做当时看起来有用的任何事情。这种方法在本质上非常实用,只有在前一个月左右编写的页面才是可维护的;否则,我们将无法记住它是如何工作的,并将不得不重新计算代码。

一个更合理、更易维护的方法是让三者中的每一部分都做自己最擅长的事情。虽然这三者必须相互重叠,但是有可能(而且肯定是可取的)让它们以一种使阅读和维护代码更加容易和快速的方式重叠。图 2-5 显示了这三个关注点之间更好的关系。

images

***图 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 显示初始状态(用户将鼠标移动到标题或图标上之前)。

images

***图 2-6。*交互示例初始状态

当访问者悬停在标题或图标上时,鼠标向下滑动会出现描述,如图图 2-7 所示。

images

***图 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 就有意义,否则就没有意义。

images 注意本章中没有一条规则是专门针对 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…](http://www.websiteoptimization.com/speed/tweak/psychology-web-performance/)

在我们看来,每 100 毫秒销售额下降 1%都是巨大的影响。显然,页面加载时间肯定是 web 开发人员关心的一个关键问题。

指导方针

以下各节描述了一个特定的准则(按照对页面加载时间的影响顺序):

  • 减少 HTTP 请求
  • 使用内容交付网络(CDN)
  • 避免空的srchref属性
  • 添加过期标题
  • 用 GZIP 压缩部件
  • 将 CSS 放在顶部
  • 将 JavaScript 放在底部
  • 避免 CSS 表达式
  • 移除未使用的 CSS
  • 缩小 JavaScript 和 CSS
  • 最小化重绘
减少 HTTP 请求

减少 HTTP 请求是性能准则星座中的一颗闪亮的星星。这是一个复杂的主题,因此我们将其分为以下几个单独的性能指导:

  • 了解并行连接
  • 组合资源文件
  • 使用图像精灵
了解并行连接

我们注意到,减少 HTTP 请求的想法经常被忽视,尽管这是大多数网站受益的最大的性能提升。开发人员关注的是后端的复杂性,而没有意识到很多加载瓶颈都在浏览器中。因为网站的开发者不能控制浏览器做什么,但是可以控制他们自己的服务器、数据库和代码做什么,他们自然关注他们能控制的。这很好,直到开发人员无法解释浏览器是如何工作的。那就成问题了。图 3-1 说明了这个感知问题。

images

***图 3-1。*页面负载影响:开发者认知与现实

开发人员经常忽略的浏览器端限制是浏览器一次可以加载多少资源。HTTP 1.1 规范规定,“单用户客户端不应该与任何服务器或代理保持超过 2 个连接。”近年来,大多数浏览器都超过了这个建议。许多浏览器目前支持四个并行连接,少数支持六个。IE8 根据客户端的带宽改变其连接,从拨号连接的两个连接到宽带连接的六个连接。

没有什么比插图更能说明问题了,所以让我们考虑一个例子。图 3-2 显示了apple.com(通过[www.webpagetest.org/](http://www.webpagetest.org/))的加载。

images

**图 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的精灵。

images

图 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 所示。

images

***图 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 是否适合你的项目。这确实增加了成本,而且如果你的网站支持一个本地企业或者在地理上是孤立的,这就没有意义了。

images **注:**如果一个内容交付网络不能帮助你,那么它就不是真正的次优性能提示。但是,我们把它留在这里,因为如果您确实需要,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 *

images **注意:**根据网络服务器的不同,你如何设置标题和其中的字段会有很大的不同。我们展示的是需要出现在 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.cssbase.css的样式表,而与购买产品相关的页面可能包含一个名为product.cssbuy.css的附加样式表。

另一个解决方案是为站点页面使用的 CSS 规则的每个唯一组合制作一个单独的样式表。根据您的开发环境,您可以使用构建或其他服务器端脚本来创建这些文件。鉴于大多数大型网站都是动态创建页面的,创建页面的系统需要逻辑来决定使用哪种样式表。

还有一种解决方案是在请求页面时动态构建样式表。这样做的缺点是服务器端需要额外的处理,并且需要生成相同的文件名,以便浏览器可以缓存样式表。

最后,开发人员可以为 CSS 规则的每个独特组合手工制作一个样式表,并记住何时使用每一个。然而,任何依赖于人的记忆的东西都是如此不可靠,以至于我们宁愿为每个页面使用相同的样式表来降低性能。

缩小 JavaScript 和 CSS

“缩小”就是从源代码中删除所有无用的字符。对于 JavaScript 和 CSS,您应该删除所有不会破坏代码的空白(包括换行符)、不会破坏代码的所有块分隔符以及所有注释。换句话说,无情地删除任何不一定要出现的字符,以使代码工作。精简代码有助于加快加载速度和降低带宽使用率。

我们更喜欢使用缩小工具来缩小自己。人类容易出错,要么把不该去掉的东西去掉,要么把能去掉的东西留在里面。此外,通过使用工具,我们可以像我们喜欢的那样详细,包括对我们的开发伙伴的注释,并使源文件易于阅读,并且仍然相信我们会因为我们的缩小工具而获得良好的性能。

缩小工具的最后一个好处(这是大多数缩小工具的可选设置)是能够用非常短的名字替换所有的变量名,或者用短的名字替换长的东西,这是另一个节省文件大小的方法。缺点是内容很难理解——如果在这种状态下对其进行了更改,则更容易出错——在这种替换之后。不过,如果您将源文件作为冗长文件进行维护,然后只在每个版本中或者只在提交给站点访问者时进行缩减,这也没什么问题。

我们使用雅虎的 YUI 压缩器来压缩 CSS 和 JavaScript。你可以在 developer.yahoo.com/yui/compres…

为了提供一个合适的例子,我们在清单 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,当有一组节点要插入时,避免一次插入一个节点。例如,如果您希望插入一个列表,不要添加列表(ulol)元素,然后添加它的每个子元素(li)。每次插入都会强制重绘,所以添加一个包含两个条目的列表会强制进行三次重绘操作。相反,创建一个包含要插入列表的 HTML 的字符串,然后一次性插入该字符串。这样,您只需要强制执行一次重绘操作。

类似的原则也适用于动态设置元素的样式。不要在 JavaScript 中设置每个样式元素。相反,创建一个包含所有必要样式信息的类,然后在元素上设置该类。同样,您得到一个重绘操作,而不是多个重绘操作。

您确实应该避免修改 DOM。然而,如果你必须这样做(我们也被迫这样做,所以我们知道它会发生),以这样一种方式做,你不是重复地强迫重绘操作。

延伸阅读

在众多关于 HTML 和 CSS 性能优化的信息来源中,我们发现以下是最有用的:

总结

这一章介绍了一些优化网站访问者页面加载时间的方法。以下是你可以快速做出的简单改变(如果你还没有的话):

  • 将 CSS 放在head元素中。
  • 将 JavaScript 放在body元素下面。
  • 指定图像的尺寸。
  • 指定一个字符集。

当然,如果你有很多页面,仅仅因为重复,即使简单的修改也会很麻烦。

如果你有能力做出更大、更系统的改变,尝试下面的改进(如果你还没有做的话):

  • 通过以下方式减少 HTTP 请求
    • 组合您的资源文件。
    • 使用图像精灵。
  • 避免空的srchref属性。
  • 用 GZIP 压缩部件。
  • 避免 CSS 表达式。
  • 使用高效的 CSS 选择器。
  • 使 JavaScript 和 CSS 成为外部的。
  • 缩小 JavaScript 和 CSS。
  • 尽量减少重画。

最后,我们强烈建议在考虑浏览器如何加载页面的同时,定期检查网页。没有人能一直记住这一点,因为作为开发人员,我们总是会遇到一些奇怪的小问题,需要我们全神贯注。因此,每隔一段时间停下来检查一下页面负载是个好主意。如果你是团队的一员,传递指导方针,让整个团队都参与进来。

四、响应式网页设计

为了与我们指出增强 web 开发人员性能的技术的趋势保持一致,可能对开发人员来说最节省时间的方法之一是“一个代码库”的想法。本质上,它是能够使用相同的代码向桌面浏览器、平板电脑和移动设备提供体验的想法。

传统上,如果你想拥有一个网站的移动表示,你需要创建一个单独的网站,该网站根据目标设备的外形和交互模型进行定制。一般来说,这些独立的网站在代码结构上会有很大的不同,如果你为一家大公司工作,可能会由在移动领域工作的专业开发人员创建和维护。

然后,在 2008 年,W3 为 CSS3 媒体查询创建了一个规范。各种浏览器或多或少地实现了对媒体查询的支持,现在我们可以选择让我们的网站适应访问者的显示形式,而无需服务器逻辑、重定向或复杂的 JavaScript。媒体查询是一系列技术中的一种,这些技术共同创造了响应式网页设计。

在我们走得更远之前,我们需要在该表扬的地方给予表扬。Ethan Marcotte 在一篇名为 List Apart 的文章中创造了“响应式网页设计”这个术语。你可以在 www.alistapart.com/articles/re…

响应式网页设计

我们可以让我们的网站适应他们被浏览的设备,而不是让不同设备的访问者去不同的网站。一个显而易见的激励因素是开发时间,而不是为每个设备创建一个单独的站点。但是,让我们也考虑一下消费我们网站的设备的快速变化的情况。无论我们是在谈论新型号的平板电脑、智能手机或网络电视的屏幕尺寸,还是谈论全新的外形,如汽车界面,如果我们试图不断调整每个新设备的代码库,我们就会陷入无休止的追赶游戏中。要想看到令人惊叹的各种屏幕尺寸,请访问 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。*包含媒体查询的样式元素

` body {   width: 960px;

}

/* 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属性,而不用担心它会脱离屏幕或导致不受欢迎的滚动条。

此外,您可以选择向移动访问者显示不同的导航方案,这种方案更接近于本地应用导航。将桌面网站转换为移动格式时,一个常见的疏忽是忘记用户如何握持设备。因此,底部的导航允许用户使用拇指来导航。虽然将导航方案放在底部并不总是可行的,但一个可行的替代方法是在顶部使用一个列表。无论你选择顶部导航还是底部导航,你都需要避免使用侧边导航,因为这样可以减少水平空间,也可以减少错误的拇指点击。(顺便说一句,你值得信赖的作者都有一双大手,所以他们确实注意到了这种问题。)

让我们看一个稍微复杂一点的例子。以下是四个设计意图,以使我们的人造网站适合桌面浏览器,平板浏览器和移动浏览器在高和宽的方向。

images

***图 4-1。*我们在桌面浏览器上的山寨网站

images

***图 4-2。*我们平板电脑上的山寨网站

images

***图 4-3。*我们的山寨网站上横放着一部手机

images

***图 4-4。*我们的山寨网站竖着放在手机上

现在让我们看看媒体查询是如何动态地将下面的 HTML 变成四个设计意图的。

images **注意:**如果要测试移动高大上的意图,一定要使用 Firefox 以外的浏览器。它不会触发桌面浏览器中最小的视窗。

HTML

清单 4-2 显示了我们的虚拟网站的 HTML 源代码。

***清单 4-2。*我们山寨网站的 HTML】

`

     ` `                 
            
  •           Menu 1         
  •         
  •           Menu 2         
  •         
  •           Menu 3         
  •         
  •           Menu 4         
  •       
         
               
              
  •             Side Menu 1           
  •           
  •             Side Menu 2           
  •           
  •             Side Menu 3           
  •           
  •             Side Menu 4           
  •         
             
                   

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.

          

               
    
           

        Legal Text       

       

`
CSS

清单 4-3 显示了我们的虚拟网站的 CSS 文件。请注意文件底部的媒体查询(每个查询前面都有一个反映其预期目标的注释)。

***清单 4-3。*我们虚拟网站的 CSS 文件

.heroWrap {   background: #82BEFF;   min-height: 200px; } .mainNav {   background: #4B6E93; } .mainNav li {   float: left;   padding: 20px; } .mainNav a {   color: white;   text-decoration: none;   font-size: 18px; } .sideNav {   float: left;   background: #DDB14B;   width: 20%;   padding: 3%; } .sideNav li {   padding: 20px; } .sideNav a { `color: #4B6E93; } .contentWrap {   float: right;   width: 64%;   background: #D6D6D6;   padding: 3%;   min-height: 200px } .contentSection {   width: 64%;   padding: 3%;   float: left; } .tertSection {   width: 20%;   padding: 3%;   float: right;   background: #82BEFF; } .pageFooter {   background: black;   padding: 20px;   color: white; }

/*    Default Layout: 992px. */

body {   width: 960px;   margin: 0 auto;   background: rgb(232,232,232);   color: rgb(60,60,60);   -webkit-text-size-adjust: 100%; /* Stops Mobile Safari from auto-adjusting font-sizes */ }

/* Tablet Layout*/

@media only screen and (min-width: 768px) and (max-width: 991px) {

  body {     width: 712px;   }   .tertSection {     display: none;   }   .contentSection {     float:none;     width: auto;   } } /* Mobile Layout */

@media only screen and (max-width: 767px) {

  body {     width: 252px;   }   .tertSection {     display: none;   }   .sideNav {     display: none;   }   .contentWrap {     width: auto;     float: none;   }   .contentSection {     float:none;     width: auto;   } } /* Wide Mobile Layout*/

@media only screen and (min-width: 480px) and (max-width: 767px) {

  body {     width: 436px;   } }`

所有的奇迹都发生在@媒体部分。然而,正如你所看到的,这是一个相对简单和简短的 CSS 来获得一个非常健壮的框架。当然,您实际的 CSS 可能面向更多的元素,但是这个示例让您看到了这种技术提供的强大功能。

灵活的图像

现在你已经根据访问者使用的设备调整了布局和文本处理,是时候处理图像了。您可以再次依靠 CSS 来调整图像的大小。这确实可行;但是,您将把桌面大小的图像下载到移动设备上。这远非最佳体验,尤其是在移动用户处于低带宽的情况下。此外,你应该是好人:不要把你的访问者推向他们的数据上限(和他们的 ISP 的额外费用)。

也就是说,您可能会发现自己无法选择建立服务器端解决方案(这个伟大的解决方案将在后面描述)。在这种情况下,有一个更好的独立工作的客户端方法,使服务器端解决方案非常有效。我们将从客户端方法开始。

CSS 方式

Richard Rutter 创造的技术使用简单的 CSS 属性“max-width”来完成繁重的工作。清单 4-4 展示了它有多简单。

***清单 4-4。*设置图像的max-width属性。

img {   max-width: 100% }

说实话,就这么简单。唯一的技巧是不要在图像标签或 CSS 中声明高度或宽度。发生的情况是,图像以其原始大小显示,然后如果容器小于原始图像大小,则缩小到其包含元素的宽度。就像魔法一样。

images **注意:**CSS 方式的一个问题是,你必须通过使用一个条件语句,将宽度属性设置为 100%,来考虑 IE6 和 IE8 缺乏“最大宽度”支持的问题。

此外,这种技术不会节省带宽,因为它不会改变图像文件的大小。

简单的服务器端解决方案

实现这一点的服务器端方法是拥有同一个图像的多个版本,并提供适合每个访问者使用的设备的图像。

这个聪明的方法是由负责建立 BostonGlobe.com 响应网站的 Filament Group 创造的。出于对其他 web 开发人员的善意,他们已经创建了成功使用这种方法所需的文件,并将它们存储在[github.com/filamentgroup/Responsive-Images](https://github.com/filamentgroup/Responsive-Images)中。这个解决方案的唯一(但也是最重要的)限制是它只适用于 Apache web 服务器。然而,通过一些逆向工程,您可以在其他服务器环境中实现类似的解决方案。

你必须做一些额外的工作,以确保你的网站上的每一个全尺寸的桌面浏览器图片都有一个更小的适合移动设备的图片。那么开始使用这个解决方案就相对容易了。清单 4-5 显示了响应图像的样子。

***清单 4-5。*一个响应的图像元素

<img src=" img/running-sml.jpg?full= img/running-lrg.jpg" />

因此,您将在查询前包含较小的图像路径,在查询字符串中包含较大的图像作为“full”的值。由于您仍然使用前面部分描述的 CSS 方式中的最大宽度技术,它的大小非常合适,但是现在您调整了一个小得多的图像的大小,并且没有强迫使用移动设备的访问者下载巨大的图像。这是一些移动的善良。

如果你想知道引擎盖下发生了什么,这里有 git 页面上的解释 :

rwd-images.js(我想他们的意思是:responsiveimgs.js)一加载,它就测试屏幕宽度,如果屏幕很大,它就在页面的头部插入一个基本元素,通过一个名为“/rwd-router/”的虚构目录来引导所有后续的图像、脚本和样式表请求。当这些请求到达服务器时。htaccess 文件确定请求是否是响应图像(它是否有?完整查询参数?).它会立即将有响应的图像请求重定向到它们的完整大小,而所有无响应的图像请求会通过忽略“/rwd-router/”段的 URL 重写到达它们正确的目的地。

斯科特·杰赫,github comment

尽管很聪明,但还有一些额外的警告。如果不支持的浏览器请求该图像,它将获得这两个图像。目前,该解决方案支持 Safari(桌面、iPhone、iPad)、Chrome、Internet Explorer (8+)、Opera 和 Firefox 4。此外,如果浏览器中禁用了 JavaScript,使用桌面的访问者将获得移动大小的图像。虽然这两者都不理想,但总比没有好,明智的降级决定也是如此。

柔性网格

在本章前面介绍的响应式网页设计的例子中(在清单 4-2 和 4-3 ,我们使用响应式设计来实现四个设计意图。CSS 通过检测屏幕宽度并使用该值来决定使用哪种布局来实现这个目标。当你观察它的工作时(通过改变浏览器窗口的大小),它会在每个布局中产生很大的变化。这种方法的缺点是,您锁定了可能不完全匹配的屏幕分辨率的布局。如果你的目标是,比方说,一部 iPhone,这可能是可以接受的,但是你没有尽你所能让你的网站适应未来。

输入弹性网格。你可以在960.gs/了解更多关于使用基于 CSS 的网格的知识。我们不会深入 CSS 的本质,但是我们会给你一个基于 CSS 的网格系统的概述。

基本上,灵活的网格允许创建一些虚拟的列和这些列之间的檐槽。假设内容排成 12 列网格,如图图 4-5 所示。

images

***图 4-5。*带水槽的 12 列网格

可以在元素上设置完全适合列网格的预定义类。这些类类似于“colOne”、“colTwo”到“colTwelve”。为了清楚起见,我们想指出“colOne”并不是指第一列。它引用一列宽的元素,该元素很可能在第 10 列或其他地方。类似地,列 2 引用两列(包括列之间的装订线)宽的元素,并且可能跨越任意两列。

然后,如果你想要一个占据整个页面宽度的 div,你可以创建一个类似于清单 4-6 中所示的元素。

***清单 4-6。*穿过整个网格的div元素

<div class="colTwelve"></>

结果将类似于图 4-6 中的。

images

***图 4-6。*一个穿过网格中所有列的 div

类似地,如果你想要三个相等的列(它们之间有间距),你可以创建类似于清单 4-7 中所示的元素。

***清单 4-7。*网格中三个相等的列

`

`

结果将类似于图 4-7。

images

***图 4-7。*三根带檐槽的相等柱子

增加灵活性

[cssgrid.net/](http://cssgrid.net/)中可以找到灵活电网系统的一个很好的例子。那个网格系统是基于百分比的。因此,网格会根据窗口的大小进行伸缩。现在,您用“colFour”类创建的所有div元素都随浏览器宽度缩放。然后,当满足特定宽度标准时,您可以根据需要使用媒体查询来更改素材。在本书的后面,您将看到如何使用 1140 CSS 网格来实现一个稍微宽一点的网格,这个网格对于小型设备来说仍然可以很好地伸缩。你可以在[cssgrid.net](http://cssgrid.net).找到 1140 CSS 网格

总结

如果你实现了响应式的网页设计,而不是预定义的布局,你的网站会随着浏览器的大小而伸缩。这种方法有一些缺点。当谈到像素的完美时,你必须适应一定程度的失控,因为你的设计必须在其约束条件下更加灵活。你可以通过设计中的巧妙方法来克服这一点。为此,您可以定义没有固定宽度的元素(更确切地说,如果您需要宽度,宽度就是百分比),并随着显示的变化而改变字体的大小。这样,像标题这样简单的东西就不会突然变成一个巨大的多行块,因为你的访问者碰巧使用了一个小显示屏的设备。然而,它确实需要更多的时间来开发和测试。但是这项工作的回报是一个真正的适应性网站。再一次,为了看到一个适应性网站的好例子,仔细看看[www.bostonglobe.com](http://www.bostonglobe.com)

在本书的后半部分,我们将开发示例电子商务网站,我们将使用响应式 web 设计作为网站的基础之一,所以稍后您会看到这些技术。

五、理解 Web 重用模式

当 Mike 刚开始为美国五大电子商务网站之一工作时,他提出了一种开发方法,这种方法加快了开发周期和更新周期(或者说减少了维护时间)。这个网站有很多产品和很多展示这些产品的方式,这取决于访问者在网站中的位置(搜索还是按类别浏览还是按价格浏览,等等)。

最初,Mike 确定了 14 种不同的产品设计意图。由于站点是在 MVC 平台上构建的,开发人员制作了 14 个不同的视图(MVC 视图是一个 HTML 片段,通过特殊的语法嵌入了服务器端逻辑),每个视图对应一个意图(也称为处理)。每次治疗创建一个视图是解决该问题的一种常见方法。首先,每个处理的一个视图清晰地映射(支持开发团队内部和之间以及开发和设计团队之间的交流)。另一方面,至少在开始阶段,它能够快速发展。

不幸的是,为每个处理创建一个视图很快被证明是一个糟糕的范例(按照许多开发人员的说法,是一种反模式)。一旦您需要更改组成各种治疗的元素的某个方面,您突然需要更改许多视图,而不是一个视图。例如,如果业务涉众决定添加一些东西,Mike 必须更改 14 个视图。抛开同样的事情做 14 遍的痛苦(还有哪个开发者不讨厌重复?),漏掉什么东西的几率就上去的很快。在 13 个地方而不是 14 个地方进行更改太容易了,最终会在某个地方出现错误。

在我们向您展示如何避免这种混乱之前,我们将首先详细展示这个问题。图 5-1 显示了 Mike 确定的设计变更(当然,删除了所有公司和产品信息)。

images

***图 5-1。*设计变更

当 Mike 检查这些设计意图时,他不禁注意到它们都有相似的项目,只需要改变它们的布局。他很快意识到这是 CSS 的工作,而不是一堆单独的视图。

images 注意迈克想感谢妮可·沙利文,她也经历了类似的过程,当时她重组了脸书的内容,并发现大量内容都归入了几个模式。她对这个过程的描述(在网上很多地方都可以找到)极大地启发了 Mike 在这个类似问题上的工作。

Mike 发现了各种设计意图的相似之处。图 5-2 通过颜色编码显示了相似之处。

images

***图 5-2。*设计的相似性

如你所见,所有这些设计意图共享一个图像、一个标题和一个描述。此外,大多数人还分享定价信息和行动呼吁按钮。我们创建了一个包含所有元素(图像、标题、定价信息、描述和行动号召)的动态主产品堆栈,而不是创建不同的 HTML 片段来在我们的大型高带宽电子商务网站中重复使用。然后,我们在每个元素周围放置条件语句,这样我们就可以控制它是否在被推送到客户浏览器的 HTML 中呈现。我们在父元素中插入一个 CSS 类来实现我们的设计意图。我们对此并不过分严格。如果出现了不适合我们现有模式的新产品堆栈处理,我们总是可以选择创建另一个视图。也就是说,在一年半的时间里,我们被要求进行大量的更改和六次额外的处理,我们只需要创建一个额外的视图(对于一个可切换的功能,我们认为它超出了我们的产品堆栈视图的范围)。

让我们来看一些代码。清单 5-1 显示了产品堆栈背后的 HTML(的简化版本)。

***清单 5-1。*产品堆栈的 HTML】

`

          

Title

    
          
  •         LabelPrice       
  •       
  •         LabelPrice       
  •       
  •         LabelPrice       
  •     
    
      

Description

      
            
  • Bullets
  •       
    
           Button        

`

虽然您可以复制和粘贴框架,并手动填充文本和图像路径,但更有效的方法是使用您选择的服务器端解决方案,并动态填充产品堆栈。在 Mike 和 Jay 相遇的前五大公司,解决方案是微软 MVC 框架。这是维护方面真正获得性能提升的地方,因为现在我们只是调整一段代码,这段代码将更新我们所有的产品堆栈。

因为我们使用的是微软的 MVC,我们的视图是。NET MVC Razor 代码(实际上只是嵌入了某些代码的 HTML)。然而,任何类似的服务器端语言(如 JSP 或 PHP)都应该允许完成相同的任务,因为我们所做的只是设置一些变量,并使用一些逻辑来决定在最终页面中包含哪些 HTML 内容,然后通过管道发送给客户。

出于演示目的,我们将在视图中创建一些变量,但是您通常会从数据库或模型中填充这些变量。此外,为了清楚起见,我们将在定价信息中排除逻辑。清单 5-2 显示了产品堆栈的 Razor 代码。

***清单 5-2。*剃刀码为一个产品栈

`@{   var Treatment = "psTreatmentA1";   var Title = "A Great Thing to Behold";   var ImagePath = "img/image.jpg";

  var RetailPriceAmt = "499.99";  varDiscountAmt="499.99";   var DiscountAmt = "100.99";   var YourPriceAmt = "$399.99";   var Description = "Sartorial williamsburg small batch helvetica mixtape wayfarers. Art party biodiesel.";   var showPricingWrap = true;   var showDescBullets = true;   var showButton = true;

}

          

@Title

    if(showPricingWrap){     
          
  •         Retail Price:@RetailPriceAmt       
  •       
  •         Discount:@DiscountAmt       
  •       
  •         Your Price:@ YourPriceAmt       
  •     
    }     
      

Description

      if(showDescBullets){       
            @foreach(Bullet bullet in Model.Item1)         {           
  • @bullet.bulletText
  •         }       
      }     
    @if(showButton){            Button          }    `

布尔逻辑让我们只发送我们特殊处理所必需的代码,这样我们就不会有发送不必要的代码然后用 CSS 隐藏它的开销。通过这种方法,我们在代码匹配的模式上获得了很大的灵活性。

最棒的是,通过简单地给我们的section element with the class: “productStackWrap”添加一个类名,我们可以表达各种各样的设计意图。请记住,我们对每个 CSS 转换都使用相同的 HTML。

images 注意我们利用 CSS 嵌套来完成繁重的工作。我们相信 CSS 会优雅地失败。也就是说,如果浏览器的 CSS 渲染引擎不理解我们的 CSS 中的某些内容,它会忽略它(只要它的语法正确)。我们大量使用 CSS 嵌套。

清单 5-3 展示了一个 CSS 嵌套的例子。

***清单 5-3。*CSS 嵌套的例子

.child {   font-weight: normal; } .parent .child {   font-weight: bold; }

如果有一个类为child的元素有一个类为parent的祖先,那么child应用的元素是粗体的。否则,child 将保留其font-weight: normal属性。通过将parent”的类设置为child的祖先,我们可以控制各种元素的字体粗细。

现在让我们回到为多种设计设计产品堆栈。清单 5-4 显示了所有处理共享的基本 CSS。

***清单 5-4。*所有产品堆栈处理的基本 CSS

/* Shared Product Stack Rules */ .productStackWrap {   position: relative;   font-size: 14px;   margin: 40px 0;   border: 1px solid #999;   padding: 20px; } .psTitle {   font-size: 21px;   font-weight: 700;   margin: 0 0 5px 0; } .psPriceWrap {   padding: 0; } .psPriceWrap li {   list-style: none;   padding: 2px 0; } .spLabel {   display: inline-block;   width: 75px;   text-align: right;   padding-right: 3px; } .spAmount { `  display: inline-block;   width: 75px;   text-align: right;   font-weight: 700; } .spTotalPrice {   color: green; } .spDescriptionWrap p {   line-height: 150%; }

.psCTA {   background: green;   color: white;   padding: 5px 0;   width: 120px;   display:block;   border: 0;   text-align:center;   font-size: 20px;   text-shadow: 1px 1px 1px #666666;   -webkit-border-radius: 5px;   -moz-border-radius: 5px;   border-radius: 5px;   -moz-background-clip: padding;   -webkit-background-clip: padding-box;   background-clip: padding-box;   background: #92c436; /* Old browsers /   background: -moz-linear-gradient(top,  #92c436 0%, #97c64b 50%, #80c217 51%, #7cbc0a 100%); / FF3.6+ /   background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#92c436), color- stop(50%,#97c64b), color-stop(51%,#80c217), color-stop(100%,#7cbc0a)); / Chrome,Safari4+ /   background: -webkit-linear-gradient(top,  #92c436 0%,#97c64b 50%,#80c217 51%,#7cbc0a 100%); / Chrome10+,Safari5.1+ /   background: -o-linear-gradient(top,  #92c436 0%,#97c64b 50%,#80c217 51%,#7cbc0a 100%); / Opera 11.10+ /   background: -ms-linear-gradient(top,  #92c436 0%,#97c64b 50%,#80c217 51%,#7cbc0a 100%); / IE10+ /   background: linear-gradient(top,  #92c436 0%,#97c64b 50%,#80c217 51%,#7cbc0a 100%); / W3C /   filter: progid : DXImageTransform.Microsoft.gradient( startColorstr='#92c436', endColorstr='#7c bc0a',GradientType=0 ); / IE6-9 */ } .descBulletsUL li {   padding: 4px 0; }`

为了让基本产品堆栈 HTML 看起来像我们的第一个设计意图,我们将把类psTreatmentA1添加到父元素中。当然,那是不完整的。当我们进行治疗时,我们将完成每种治疗的列表。请耐心等待,我们将很快展示如何使用基础治疗。图 5-3 显示了结果。

images

***图 5-3。*产品堆叠处理 A1

清单 5-5 显示了处理 A1 的 CSS。

清单 5-5。 CSS 治疗 A1

`/* Treatment A1 */ .psTreatmentA1 {   width: 310px; }

.psTreatmentA1 .psImage {   width: 80px;   height: 112px;   float:left; }

.psTreatmentA1 .psTitle {   margin-left: 100px; }

.psTreatmentA1 .psPriceWrap {   margin-left: 100px; }

.psTreatmentA1 .psCTA {   margin: 0 auto; }

.psTreatmentA1 .psPriceWrap {   text-align: right; }`

类似地,我们可以添加类“psTreatmentB1”来重新排列内容以匹配另一个设计。图 5-4 显示了结果。

images

***图 5-4。*治疗 B1

清单 5-6 显示了治疗 B1 的 CSS。

***清单 5-6。*治疗 B1 的 CSS

`/* Treatment B1 */ .psTreatmentB1 {   width: 190px;   padding-right: 156px; }

.psTreatmentB1 .psImage {   width: 116px;   height: 151px;   position: absolute;   right: 20px;   top: 20px; }

.psTreatmentB1 .psPriceWrap {   text-align: left; }`

同样,我们可以添加类“psTreatmentC1”来重新排列内容,以匹配第三个设计。图 5-5 显示了结果。

images

***图 5-5。*治疗 C1

清单 5-7 显示了 C1 治疗的 CSS。

清单 5-7。 CSS 治疗 C1

/* Treatment C1 */ .psTreatmentC1 {         width: 385px; } `.psTreatmentC1 .psImage {         width: 386px;         height: 287px;         display: block;         margin-bottom: 20px; }

.psTreatmentC1 .psPriceWrap {         text-align: left;         margin-left: 110px; }

.psTreatmentC1 .psTitle {         text-align: center;         font-size: 27px; }`

我们将用一个最简单的设计来结束我们的例子。图 5-6 显示了结果。

images

***图 5-6。*治疗 D1

清单 5-8 显示了 D1 治疗的 CSS。

清单 5-8。 CSS 治疗 D1

`/* Treatment D1 */ .psTreatmentD1 {   width: 261px;   padding-left: 224px;   min-height: 116px; }

.psTreatmentD1 .psImage {   width: 184px;   height: 114px;   position: absolute;   top: 20px;   left: 20px; } .psTreatmentD1 .spDescriptionWrap p {   margin-bottom: 0; }

.psTreatmentD1 .psPriceWrap, .psTreatmentD1 .psCTA, .psTreatmentD1 .descBulletsUL {   display:none;   /* If we’re using our server-side logic, we wouldn’t need this as these extraneous HTML elements wouldn't be on the page. */ }`

关于 Web 重用的一些最终观察

思考积木

我们喜欢所有那些能够用一堆相似的零件做东西的玩具。我们还认为这是构建 web 组件的一个很好的比喻,您可以在您的站点中重用这些组件。如果你的组件是一个玩具积木的集合,那么构建你的站点就相当于挑选要组装的积木。当一块砖的形状相同但颜色不同时,这类似于一个 web 组件打开或关闭了一个或多个功能。

从构建块和创建可重用的 web 组件的角度来考虑,应该可以加快开发速度,将更多的任务交给初级开发人员,并随着业务需求的变化减少更改页面的时间。理想情况下,一旦你弄清楚了网站需要的所有组件(以及这些组件的变体),添加新页面和更改现有页面应该是轻而易举的事情。

一切都包含在内

容器有很多名字。在我们自己的工作中,我们经常谈论各种各样的“堆栈”。正如我们在本章前面所演示的,文章堆栈是一个容器,它保存了实现一篇文章的元素集合,以供访问者阅读。类似地,我们还展示了一个产品堆栈(以及它的一些变体)。对我们来说,甚至一个按钮也是一个堆栈,因为按钮通常有多个元素(比如为按钮定义一块空间的 div 元素,如果按钮是一个链接,还有一个锚元素,一个图像按钮的图像元素,等等)。然而,“栈”实际上只是容器的另一个名称。

我们想以一个提醒来结束我们关于可重用 web 组件的讨论,在一个 web 页面上,几乎所有的东西都在某种容器中,并且几乎所有的东西都被其他容器所包含。不管你怎么称呼它(栈或小部件或其他什么),每个组件实际上都是一个容器,既包含其他容器,又被其他东西所包含。我们发现,开发和维护功能丰富的网页可以变得更易于管理,只要记住这一切都只是盒中盒。

总结

在这一章中,我们讨论了创建一组组件的想法,这些组件覆盖了一个企业网站的所有需求,然后用这些组件的一部分来制作每个页面。我们强烈建议所有主要网站使用类似的技术,因为它提供了以下好处:

  • 它确保了大型复杂网站的品牌一致性。对许多公司来说,他们的品牌是他们最宝贵的素材,对品牌素材(标识、口号等)的一致对待。)是网站开发和维护的重要组成部分。
  • 它很容易在整个网站上创造一致的体验。事实上,您必须做额外的工作来获得标准组件所提供的外观。
  • 它使得开发新的网页和维护现有的网页变得更加容易和快速。
  • 它允许将日常的 web 开发任务交给经验较少的开发人员,他们只需将标准组件组装起来就可以完成。
  • 它解放了高级开发人员,使他们能够解决棘手的问题,创建定制的解决方案,并在业务需要时创建额外的站点范围的组件。

我们发现这种技术非常有效。随着创建和维护大型复杂网站的开发人员对它的了解,我们希望看到它被广泛采用。

六、页面模板

在本书的整个第二节中,我们将重点创建一个示例站点来展示我们在第一节中讨论的原则。阅读一些东西是如何工作的只会让大多数人开始真正理解。看到它的实际应用是迈向精通的一大步。(当然,要完全学会如何做一件事,你必须自己去做,但我们相信你会这样做。)为了给对话提供一些背景信息,在开始介绍网站的构建之前,让我们先来看看这个网站。图 6-1 显示了我们正在建设的样本网站,你可以在[clikz.us](http://clikz.us)找到

images

***图 6-1。*我们正在建设的样本网站( clikz.us )的

我们将通过首先创建一个页面模板来开始构建我们的示例网站。在。NET 世界中,这样的模板通常被称为母版页。我们也看到它被称为主布局。不管它叫什么,页面模板是我们构建网站的框架。

对于我们的示例站点,我们将创建一些设置各种选项的元素,并指定一个灵活的网格。在它们之间,这些项目构成了网站上所有页面的基础。

设置样板文件选项

首先,我们将主要依靠 www.html5boilerplate.com`` 提供的工具来生成一个不错的 HTML5 起点。HTML5 样板网站提供了很多我们在本章中不会涉及的配置细节,包括 web 配置、构建脚本等等。由于我们在本书中关注的是 HTML 和 CSS,所以我们在示例站点中省略了这些特性。我们强烈建议您详细查看 HTML5 样板网站,寻找可能对您有用的内容。我们也要感谢保罗·爱尔兰、迪维娅·马年、施尚恩、马蒂亚斯·拜恩斯和尼古拉斯·加拉格尔(仅举几例)为我们提供了这个有用的工具。他们做了有益于所有网络开发者的伟大工作。

在我们分析代码之前,您可能会看到一个完整的样板页面。清单 6-1 显示了这样一个清单。

***清单 6-1。*一个完整的页面模板

`

  

     

     

  

  

  

     

  **   

  

  

  

  

  

  

        

           

     

`
HTML 元素周围的条件语句

通常(通常,我们怀疑),您需要识别您站点的个人访问者正在使用的浏览器,以便您可以向该访问者提供最好的体验。将 HTML 元素包装在条件语句中允许您这样做,尽管只适用于 Internet Explorer。

images **注意:**我们在这一章中对 Internet Explorer 的明显关注实际上只是以下事实的一个副作用:Internet Explorer 迫使 web 开发人员做额外的工作来在 IE 上工作,以及 IE 是唯一支持条件语句的浏览器(至少目前如此)。鉴于世界上大部分地区仍在使用 IE,我们认为它的好处是值得花时间的。你自己可能也遇到过同样的问题。

清单 6-2 展示了如何做到这一点。

***清单 6-2。*条件语句识别浏览器

`

  `

乍一看,这可能令人困惑。此外,仅仅是呈现一个 HTML 元素似乎就需要很多代码。然而,对于网站的开发者和最终的访问者来说,这将是无价的。

首先,让我们考虑条件语句本身。下面一行显示了一个示例:

*<!--[if IE 7]>  Render this html<![<ins>endif</ins>]-->*

语法相当明显,但需要解释一下,以便澄清一些细节。第一,是评论。这很方便,因为不识别条件语句的浏览器(除了 Internet Explorer 以外的任何浏览器)都会忽略该语句(因此对于使用其他浏览器的访问者来说,不会有页面加载延迟)。二、以<!--[if *condition*]>开头,以<![endif]-->结尾。最后,如果浏览器识别出条件语句,并且浏览器能够将该语句解析为真,则中间的代码将运行。

在清单 6-2 所示的例子中,如果访问者的浏览器是 Internet Explorer 7,中间的代码就会运行。在清单 6-2 所示的例子中,没有操作符。但是,运算符(如“小于”和“大于”)可以存在。默认的操作符是“equals”,但是没有它的语法。如果你需要“等于”,省略掉操作符。让我们来看看一些比较常用的运算符。表 6-1 显示了基本操作符。

images

images

如清单 6-2 所示,我们在这些条件语句中包装了 HTML 元素的许多版本,但是浏览器将只处理一个版本:浏览器可以评估为真的版本(对于 Internet Explorer)。例如,如果我们的访问者的浏览器是 IE7,HTML 元素的定义如下所示:

<html class="ie7" <ins>lang</ins>="<ins>en</ins>">

现在让我们仔细检查我们的条件语句的最后一行,在下面一行中再次显示(我们讨厌翻阅页面):

<!--[if (gt IE 9)|!(IE)]><!--> <html  lang=”en”>  <!--<![endif]-->

附加注释确保了,如果访问者使用 Internet Explorer 以外的浏览器,浏览器可以找到 HTML 元素的开始标记。此外,如果访问者使用 Internet Explorer,此行将匹配高于 9 的版本。IE10 在测试中,所以有可能。

条件的最后一部分是|!(IE)。也就是说“或者不是 Internet Explorer”竖线(|)表示“或者”,感叹号(!)表示“不是”我们并不严格需要这种语法,因为除了 Internet Explorer 之外的任何浏览器都无法识别它。然而,我们包含它是为了与开发人员(包括我们自己)交流,他们可能在将来的某一天需要维护这个页面,以防其他浏览器开始识别条件语句。

我们希望 IE10 是符合标准的,我们不需要用一个条件来捕捉它,也不需要为它编写特殊的规则。然而,如果确实需要为 IE10 编写特殊的规则,我们可以在我们的条件语句集合中添加另一行。清单 6-3 显示了在这种情况下最后两个条件是什么。

***清单 6-3。*会计 IE10

`

<**html**  lang=”en”>  `

无论我们是检查 IE10 还是停止在 IE9,呈现给任何不符合任何条件的浏览器的元素的开始标记都显示在下面的行中:

<html  lang=”en”>

现在我们已经展示了语法是如何工作的,我们可以深入了解为什么这种技术是有价值的。您可能已经注意到,条件语句(除了最后一个)向 HTML 元素添加了一个类。此类别对应于访问者使用的 Internet Explorer 版本。就其本身而言,添加一个类没有任何作用。然而,它为针对特定于浏览器的 CSS 提供了一个很棒的钩子。例如,如果我们需要为 IE7 调整一些填充,我们可以编写如清单 6-4 所示的 CSS。

***清单 6-4。*为所有浏览器和 IE7 定义填充

`.paddingDefinition {   padding: 10px  /* All Browsers */ }

.ie7 .paddingDefinition {    padding: 12px; /* Only IE7 */ }`

我们不需要编写一堆 CSS 代码或加载额外的特定于浏览器的样式表(这将意味着更多的 HTTP 请求和更差的性能),我们可以简单地在现有代码旁边定义我们的特定于浏览器的代码,这样更容易找到和理解。此外,通过添加一个额外的选择器,我们增加了特异性,因此我们的 IE7 规则比现有的类具有更大的权重——但前提是浏览器是 IE7。这又回到了浏览器忽略没有意义的 CSS 选择器。因此,如果我们的条件语句没有将类“ie7”添加到 HTML 标记中,那么第二条规则(" . ie7。someClass”)从未得到应用。

我们喜欢 CSS 让我们编写浏览器可以忽略的规则。这听起来很奇怪,但确实有效。如果你想了解这一强大技术的更多信息,请访问[paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/](http://paulirish.com/2008/conditional-stylesheets-vs-css-hacks-answer-neither/)

设置字符集

正如我们在第三章中讨论的,你应该总是将 charset(“字符集”的缩写)元标签设置为你的 HTML 的 head 部分的第一项,因为不这样做可能会导致在浏览器开始向你的访问者显示信息之前的长时间停顿。下面一行显示了将字符集设置为 UTF-8 的语法:

<meta charset="utf-8">

不这样做可能会对性能和安全性造成严重后果。如果浏览器不确定使用什么字符集,它会尝试使用不同的算法(因浏览器而异)来分析或“嗅探”类型。这种嗅探可能会延迟页面加载,并为攻击者提供了一种欺骗浏览器使用 UTF-7 字符集的途径,这种字符集有很大的漏洞。套用一句旧的电视广告:UTF-8-没有它不要离开家。

控制 IE 的兼容模式

从 IE8 开始,微软引入了“兼容模式”以及任何网页打开和关闭兼容模式的方法。当兼容模式打开时,如果浏览器检测到它不理解的东西,它将恢复到以前的 IE 浏览器规则。

images **提示:**微软有一个“兼容性视图列表”,这是微软的人认为需要用 IE7 引擎渲染的网站列表。要查看您的网站是否在该列表中,请访问以下网站:ie9cvlist.ie.microsoft.com/ie9CompatViewList.xml

如果你想在兼容模式下测试你的站点,你可以通过 ie 浏览器中可用的开发者工具手动强制一个页面在兼容模式下显示(按下 F12 或者查看工具菜单)。您还可以使用兼容模式来测试跨浏览器代码。然而,不要单独依赖这种技术,因为它不是 100%可靠的;在各种浏览器中查看和测试页面仍然是无可替代的。图 6-2 显示了在 Internet Explorer 中可以找到浏览器模式和文档模式设置的地方。从这些列表中,您可以选择“兼容模式”

images

图 6-2。 Internet Explorer 的开发者工具

有关兼容模式的更多信息,请访问 http://msdn . Microsoft . com/en-us/library/DD 567845(v = vs . 85). aspx。

通常,我们更喜欢关闭兼容模式。为此,我们使用如下行所示的元元素:

<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">

images **注意:**前面的元元素会导致验证问题。通常最好在您的中添加一行。设置兼容模式的 htaccess(或等效)文件。

属性告诉 Internet Explorer (IE8 和更高版本)这个元素将设置兼容模式。属性指定了哪些显示规则(IE8、IE9 等。)供访问者使用 Internet Explorer 时使用(同样,仅限 IE8 及以上版本)。

内容属性中的第一个设置IE=edge告诉访问的 IE 浏览器不要使用兼容模式,而是使用可用的最新版本。例如,如果你的访问者的浏览器是 IE9,浏览器应该使用 IE9 规则来呈现页面。

这个 metatag 的第二部分,chrome=1,是告诉 IE,如果用户安装了插件,它可以使用 Google Chrome 框架(GCF)。如果你不熟悉 GCF,它是一个很棒的免费 Google 插件,可以让 IE 浏览器在 IE6 中显示 HTML5 代码。它基本上在 IE 中创建了一个使用 Chrome 引擎的框架。我们喜欢它。不幸的是,没有办法确保所有使用 IE 的访问者都安装了 GCF(但是请参见下一节获得这方面的帮助)。您可以在以下网站找到关于 GCF 的更多信息:www . chromium . org/developers/how-tos/chrome-frame-getting-started。

提示安装谷歌 Chrome 框架

正如我们在上一节中提到的,你不能保证使用旧版本 Internet Explorer 的访问者已经安装了 Google Chrome Frame (GCF)。但是,您可以提示他们安装它。如果您在 HTML5 样板网站上启用这个设置,您会得到一个条件元素,提示使用旧版本 Internet Explorer 的访问者安装 GCF,如清单 6-5 所示。

清单 6-5: 提示访问者安装 GCF

<!--[if <ins>lt</ins> IE 7]><p class=<ins>chromeframe</ins>>Your browser is <em>ancient!</em> <a <ins>href</ins>="http:// browsehappy.com/">Upgrade to a different browser</a> or <a <ins>href</ins>="http://www.google.com/chromefra me/?redirect=true">install <ins>Google</ins> <ins>Chrome</ins> Frame</a> to experience this site.</p><![<ins>endif</ins>]-->

当然,您可以用适合您站点的消息来修改文本。但简而言之,这个条件语句在 IE7 之前的 IE 浏览器上使用,并提供了一种安装 Google Chrome 框架的方法。这样,更多的访问者可以体验 HTML5,这很可能比 IE6 更好。

控制 iPhones 上的视窗

mobile safari(iphone 自带的浏览器)会检测页面的宽度,并缩放页面以适应手机屏幕。大多数网页都被设计成可以在桌面浏览器上浏览(尽管随着越来越多的网页设计师采用移动优先的工作方式,这种情况正在迅速改变)。因此,如果不缩放,页面通常是不可读的,因为文本太小。您可以防止 MobileSafari 缩小,而是以 100%显示内容,从而通过使用下面一行中显示的 meta 元素使内容更加易读:

<meta name="viewport" content="width=device-width">

加载 jQuery

如果您的站点使用 jQuery(顺便说一下,我们喜欢 jQuery),您可以使用以下脚本元素将其添加到页面中。

***清单 6-6。*向页面模板添加 jQuery

`

`

第一个脚本元素试图从 Google CDN 下载 jQuery 1 . 7 . 1 版本。第二个脚本元素指定一个本地目录作为脚本文件的源。

使用谷歌 CDN 有几个很大的好处。首先,你得到了 CDN 的好处。正如我们在第三章中讨论的,CDN 可以通过将素材放置在地理上更靠近用户的位置来真正提高性能。第二个也是更有趣的方面是,由于从 Google CDN 加载 jQuery 非常流行,所以您网站的访问者很可能已经访问过使用 Google CDN jQuery 库的网站。在这种情况下,它已经在缓存中,不需要下载。如果访问者带着这个 jQuery 库的缓存,他们就不需要再次下载了。我们节省了带宽和讨厌的 HTTP 请求。谈论一个性能助推器。

但是,如果谷歌宕机了怎么办(不太可能,但你永远不知道),或者(更有可能的是)如果我在没有互联网连接的情况下工作,而我想写代码怎么办。第二个脚本元素说,“如果我不能从 CDN 获得 jQuery,就使用位于我的相对目录中的文件。”在这种默认情况下,它在 root/js/libs/中查找 jQuery。您可以将该目录设置为存储 jQuery 库任何位置。最后,您还可以定制想要使用的 jQuery 版本。

老实说,如果您打算使用 jQuery,您将很难找到更好的加载方式。

images 提示: Modernizr 的 load 特性的工作方式大致相同(好主意到处都是),所以这是加载 jQuery 的另一种方式。我们在第二章中更详细地讨论了 Modernizr。

添加谷歌分析

清单 6-7 中的脚本元素为你的页面启用了谷歌分析。

***清单 6-7。*添加谷歌分析

<script>   var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']];   (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];   g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';   s.parentNode.insertBefore(g,s)}(document,'script')); </script>

自然,你应该用你网站的谷歌分析账户 ID 替换UA-XXXXX-X

更多选项

我们已经选择了我们通常使用的选项,并且我们打算将这些选项用于我们正在构建的示例网站,作为撰写本书的一部分。然而,HTML5 样板站点有更多的选项,您可能希望在自己的站点中使用,这取决于您到底需要或想要做什么。更多细节,请参见 HTML5 样板网站[www.html5boilerplate.com](http://www.html5boilerplate.com)

设置站点的网格

正如我们在第四章中提到的,一个响应式设计的一个常见部分是一个灵活的网格。对于我们的示例站点,我们选择了安迪·泰勒的 1140 CSS 网格。Andy 创建了一个 12 列的网格,适合 1280 像素宽的显示器。由于灵活,它在较低分辨率下也看起来不错,一直到电话和其他移动设备。你可以在[cssgrid.net](http://cssgrid.net)找到 1140 CSS 网格,从[download.cssgrid.net/1140_CssGrid_2.zip](http://download.cssgrid.net/1140_CssGrid_2.zip)下载。

当你解压缩 zip 文件时,你应该得到一个类似于图 6-3 所示的目录结构。

images

***图 6-3。*1140 _ CSS grid _ 2 . zip 的内容

这本书不涉及 JavaScript,所以我们将重点放在 1140 CSS 网格的 HTML 和 CSS 部分。此外,当访问者使用识别媒体查询的浏览器时,1140 网格不需要 JavaScript 然而,它包含了谷歌在旧浏览器上处理媒体查询的脚本(称为css3-mediaqueries.js)。文件index.html很有趣,因为它显示了列的许多可能的排列。还有其他可能的安排,但是示例index.html文件中显示的排列涵盖了大多数用例。css/1140.css 文件承担了创建网格的重任。css/styles.css 文件提供了在不同分辨率下添加您自己的样式的钩子。css/ie.css 文件为 IE9 之前的 Internet Explorer 版本提供了变通方法。当使用旧版本 Internet Explorer 的访问者访问您的站点时,您应该编写一个条件语句来加载 css/ie.css。下面一行显示了这样一个语句的示例:

<!--[if <ins>lte</ins> IE 9]><link <ins>rel</ins>="<ins>stylesheet</ins>" <ins>href</ins>="<ins>css</ins>/ie.css" type="text/<ins>css</ins>" media="screen" /><![<ins>endif</ins>]-->

顺便说一下,有时跳过条件并将各种样式表合并成一个样式表是值得的。单个大型样式表比有条件地加载特定样式表性能更好,这似乎有悖常理。然而,我们最近对合并的样式表做了一些测试,发现页面加载时间更短。确实,对于页面加载时间,我们能做的最好的事情就是减少 HTTP 请求。但是,每种情况都是独特的,所以请测试您自己的替代解决方案,找出最适合您的站点的解决方案。

让我们从这个网格的一些基本概念开始。本质上,您分配类名来表示您希望您的特定元素占据多少列(总共 12 列)(即 onecol、twocol 等)。1140 CSS 网格因其最大宽度为 1140 像素而得名。这是你的站点在这个网格中所能达到的最大值。对于许多习惯使用 960 像素作为标准尺寸(以适应更小的屏幕分辨率)的人来说,这似乎有点宽。然而,由于灵活的网格,没有人再被固定的大小所束缚。如果访问者的屏幕分辨率为 1024 x 768,网格会调整所有列的大小,使 12 列仍然适合该屏幕的宽度。那是相当自由的。当然,您仍然需要在不同的分辨率下测试您的站点,以确保您对布局变化感到满意。一旦你的尺寸低于平板电脑的尺寸,你可能需要对你的布局做出一些更激进的决定。不过,在那之前,较小的柱子工作得相当好。

一旦你安装并弄清楚了网格,你就可以决定一些基本的页面结构,比如在哪里放置页眉、正文、页脚等等。最棒的是,这些元素无需任何进一步的工作就能正确调整大小。对于我们的示例电子商务网站,我们选择了图 6-4 所示的基本布局。

images

***图 6-4。*我们的样本电子商务网站的基本布局

为了实现这种布局意图,我们必须将列元素包装在几个父元素中。清单 6-8 显示了这种关系。

***清单 6-8。*父元素与网格的关系

`

  
    
  

`

类别为containerdiv元素跨越了访问者浏览器的整个宽度。类别为rowdiv元素将是你的站点的外部宽度。如果访问者的浏览器宽度为 1200 像素或更大,网格的宽度为 1140 像素(1140 网格设置的最大值)。当使用较小分辨率的访问者到达您的站点时,分辨率低于 1140 时,它的宽度会缩小。具有类twelvecoldiv元素保存你的站点内容。因为我们已经使用了最大的列数,这个div元素的宽度将是row div 宽度的 100%。这种排列可能会让您想起 HTML 表格结构,它不仅在结构上相似,在功能上也相似,因为列宽会根据可用宽度进行伸缩。container div对应table元素,row div对应tr元素,twelvecol div对应td元素。

回到我们的示例电子商务站点,清单 6-9 显示了构成站点框架的 HTML 元素。

***清单 6-9。*我们示例电子商务网站的基础结构

`

              This is the Main Header Section        

       
      This is the Main Navigation Section     
     
    
      Left Column     
    
      Content Area     
    
      Right Column     
  
              This is the Main Footer Section         `

如您所见,我们在 div 以外的元素上设置了容器、行和列类。为了保持清晰的意图,最好在块级元素上设置这些类。您还可以看到,我们已经向右边的列 div 添加了一个类last。您需要对多列布局的最后一列执行此操作。它唯一的功能是去掉右边距,这样内容就不会意外换行。

我们现在已经有了响应式电子商务网站的基本结构。在我们用控件和内容填充完新结构后,我们将进一步调整平板电脑和移动设备的布局。

总结

在这一章中,我们介绍了使用模板页面作为网站上所有(或者至少是大部分)页面的基础。为了制作这样一个页面,我们引入了由[html5boilerplate.com](http://html5boilerplate.com)背后的优秀人员提供的优秀工具。

我们还介绍了 html5boilerplate.com 工具为我们创建的各种设置,包括

  • Internet Explorer 中的条件语句。
  • 设置字符集。
  • 控制 IE 的兼容模式。
  • 提示安装谷歌浏览器框架。
  • 控制 iPhones 上的视窗。
  • 正在加载 jQuery。
  • 添加谷歌分析。

最后但同样重要的是,我们描述了我们将用于整个站点的网格系统。1140 CSS 网格在一个灵活的网格中做了我们需要的一切,甚至优雅地处理移动设备。

在本书的其余部分,我们不会涉及这些信息的大部分(除了可能在这里或那里顺便提及)。然而,当您检查我们的示例站点背后的代码时,您会在每个页面上看到这些特性。

七、导航

除非你只是制作一个页面,否则导航是你的网站的一个重要方面。因此,当涉及到网站成功的关键因素时,你真的应该考虑一下你的策略。除了有一个好的分类法,你还需要有一个清晰的方法让访问者在你的网站上找到和探索不同的产品。有时候你可能会被诱惑在你的界面中加入很多活力和重量,尤其是当你首先从设计开始的时候。但是,内容才是王道。因此,作为一个规则,你的导航不应该和你的内容争夺注意力。也就是说,你的导航应该易于查看,更重要的是,易于使用。

为了使导航方案真正易于使用,你应该支持不同类型的访问者行为。不同的人喜欢用不同的方式导航站点,你应该支持所有导航站点的常用方式,以免你赶走那些不能如他们所愿导航的用户。一本关于可用性的很棒的书,不要让我想到、 1 、有一个类比:把你的站点想象成一个百货商店。一些购物者看着部门标志,然后在过道里走来走去,直到找到他们想要的商品。有些人会找到商店员工,询问商品在哪里。第一批购物者是使用菜单找到他们想要的内容的访问者。询问店员的购物者是那些会使用网站搜索的人。您应该为这两个群体提供机制。

进一步说,百货公司把过道里的一些商品放在端盖上,这样当游客走过时,他们可以向他们展示这些商品。您应该考虑通过侧面导航这样的设计元素来提供类似的导航形式,侧面导航包括在主导航中也可用的项目(通常是下拉菜单)。我们称这种导航为“第三级导航”,因为它是我们导航方案的第三级,前两级是菜单和搜索。

在深入菜单背后的代码之前,展示一下我们在本章剩余部分创建的菜单图像是有意义的。图 7-1 显示了菜单的大部分(比页面宽度要宽,但图中显示了大部分)。

images

***图 7-1。*菜单处于就绪状态

现在,让我们来看看菜单的一些扩展形式。图 7-2 显示了游戏子菜单的放大视图。


1 史蒂夫·克鲁格,别让我胡思乱想!网站可用性的常识方法(新骑士出版社,第二版,2005)

images

***图 7-2。*鼠标悬停时游戏响应菜单

菜单结构

创建菜单结构时,您希望定义菜单内容的 HTML

  • 要有意义。
  • 可读性强。
  • 易于屏幕阅读器理解(或忽略)。
  • 非常适合渐进的设计目标。

当我们说你希望菜单使用有意义的 HTML 时,我们的意思是你希望能够查看它的源代码,并看到各种元素(容器和内容)之间的关系。当我们说您想要可读的 HTML 时,我们的意思是您不希望它因为混乱或者使用难以理解的类名而变得难以理解。当我们说你想要一个易于屏幕阅读器理解的菜单结构时,我们的意思是你想要一个不会浪费视觉障碍访问者过多时间的菜单结构(这将在本章后面更详细地讨论)。最后,当我们说你想要符合渐进设计目标的 HTML 时,我们的意思是你想要的内容不会妨碍你为你的每个目标浏览器提供最好的显示。

我们可以从大量可能的菜单结构表示中进行选择,因为 HTML 为表达这种内容提供了相当大的灵活性。为了达到这些目标,嵌套无序列表是最有意义的。由于列表中列表的方法是菜单内容的自然映射,它很容易满足有意义和可读的目标。当我们听屏幕阅读器浏览菜单时,列表中列表结构提供了菜单最直接的听觉表现。最后,我们知道我们可以让列表中的列表结构为渐进的增强目标而工作,正如我们在本章的其余部分所演示的那样。

最后,嵌套列表方法的相对自然的结构应该尽可能地面向未来。可上网设备的数量继续飞速增长,所以没有办法知道你的网站什么时候会出现在某人的冰箱上(老实说,不要感到惊讶)。

清单 7-1 显示了基本的菜单结构。

images 注:后面的类名中 nm代表navMain。同样,L2表示二级列表的组成部分。我们是节省字节的朋友,也是懒惰的打字员。

***清单 7-1。*基本菜单结构

`

`

我们使用一个非常基本的结构,你会在许多导航方案中发现(当约定工作良好时,遵循约定是好的)。在没有样式或脚本的情况下,浏览器呈现这个结构,如图 7-3 所示。

images

***图 7-3。*造型前的无序列表

您可以看到菜单结构是可读的,并且表达了正确的嵌套。在这种情况下,2 级菜单项是父 1 级菜单项的子级。我们现在可以添加样式和功能来突出您的意图。不过,在开始设计样式之前,让我们先看看提供实际菜单的 HTML。

当您阅读下面的清单时,您可能会注意到围绕第 2 层导航的额外的div元素。它的类名是nmSlideout zeroHeight。额外的标记启用了滑出技巧(我们将用菜单元素的样式来介绍)。接下来,您可能会注意到,我们在第 1 层菜单的最后一个LI元素(带有一个nmLI searchWrap类)中添加了一个搜索框。

让我们花一点时间来讨论一些基于标准的开发人员的症结所在:在这个例子中(以及我们所有的 HTML 代码中)对类名的富有表现力的使用。我们采用了这种方法,因为它很适合我们,我们现在将解释这一点。

一方面,你可能会说,通过使用 CSS 嵌套,用更少的标记就可以为嵌套元素指定类名。例如,你可以用代替一个类为nmLIli元素,而是通过声明它的父元素并如下钻取来得到那个li元素:.nmUL li。这确实会得到第一级的li元素、第二级的li元素以及其后的任何li元素。虽然像.nmUL li li这样的选择器可以用来定位那些进一步嵌套的li元素,但是这样做会变得更加复杂(并且难以阅读和维护——绝对不希望将来拖累开发)。

尽管避免令人费解的代码很重要,但这并不是使用更多代码为这些元素提供自己的类的最佳理由。渲染性能是更大的原因。使用选择元素的后代方法不会产生最佳性能。原因如下:CSS 选择器引擎从右向左工作。在使用.nmUL li选择器的情况下,浏览器首先将所有的li元素收集到一个集合中,然后尝试查找其祖先拥有一个.nmUL类的li元素。这需要在 DOM 树上爬很长时间才能发现每个li元素是否在树上的某个地方有一个.nmUL祖先。使用带有标识符的选择器可以获得更好的性能。最好的性能(但比标识符好不了多少)来自于使用 ID 属性作为选择器。使用标识符而不是 ID 属性对性能的微小影响是值得的,因为标识符可以多次使用。但是,不要因为后代选择器而接受多次上下遍历 DOM 带来的更大的性能损失。

不使用后代选择器的另一个重要原因是,这种方法将 CSS 与 HTML 的结构联系在一起。因此,如果您曾经将结构更改为ul li span span,例如,您现在已经破坏了 CSS——并且在您的导航中间有一个醒目的黑眼睛。此外,您失去了重用为其他元素定义的类的能力,这些元素可能具有相似的样式但不同的结构。

最后,对于开发人员来说,使用显式命名的元素更容易。考虑到所有这些好处的成本可能会比几千个额外的类名实例的成本要低,您会以较低的成本获得很多。

清单 7-2 显示了我们将用于导航的实际代码。

***清单 7-2。*导航 HTML

`

`

当渲染引擎完成 CSS 时,它将看起来像图 7-4 。

images

***图 7-4。*一级造型

images 注意在谈论菜单的其余部分之前,我们要指出看不见的菜单项,由class="visuallyhidden"标识。该菜单项以及其中的链接允许使用屏幕阅读器的访问者跳过该菜单。如果你曾经因为听菜单而痛苦,你就能理解为什么这个选项对有视觉障碍的游客来说是友好的。因为适应所有用户是非常重要的,所以要注意为视觉能力不同于普通访问者的人提供这种便利。

设计菜单

现在我们已经详细描述了菜单的结构和它的选择器,让我们继续把嵌套列表变成一个菜单。我们将从设计父元素ul开始。图 7-5 显示了期望的设计处理。

images

***图 7-5。*现代浏览器中的 UL 造型

我们在这种治疗中没有使用图像;换句话说,菜单完全是通过代码定义的。我们大量利用 CSS3 来获得大量额外的视觉冲击,所以你可能会合理地想,“IE8 呢?”这又引出了另一个问题,而且是一个大问题。并非所有的浏览器都需要显示完全相同的演示文稿。事实上,试图让所有浏览器显示完全相同的演示是错误的。如前所述,我们在五大电子商务网站之一工作。并让我们组织中的每个人都同意 IE6 到 IE8 的降级状态。这意味着,如果你用 IE6 到 IE8 浏览我们的网站,你仍然会看到一个很棒的网站。它只是不会有圆角和阴影。

不再坚持每个浏览器都得到相同的待遇,这大大提高了开发速度、更精简的代码、响应性更强的设计选项和更快的页面加载速度(因为没有任何额外的 HTTP 请求)。就像我们的统计数据显示的那样,旧浏览器上的方形角和无阴影框有很大的优势,但我们的访问者很少使用。

当然,这取决于你和你的雇主或顾客来决定这是否是你的一个选择,你仍然可以按照你喜欢的方式制作所有这些菜单。尽管如此,我们还是强烈建议向旧浏览器宣传降级状态的概念。这对每个人来说都是一个胜利。由于使用旧浏览器的人不经常看到尖端的设计,他们不太可能注意到他们没有在你的网站上得到它,而你获得了更干净的代码库的所有好处。当然,这并不是给使用旧浏览器的访问者一个糟糕体验的许可;他们仍然应该获得浏览器所能提供的最佳体验。正如我们的技术评论员(嗨,杰夫)指出的,这很像在黑白电视上看彩色电视节目;肯定有什么东西不见了,但这是观众得到的。电视摄制组所能做的就是努力确保画面至少能在黑白电视上显示出来。最后,随着圆角在设计界失去声望,我们都在无缘无故地追逐回报递减。

图 7-6 展示了 IE8 参观者实际看到的。它仍然提供了正确的信息和功能,看起来也不错,特别是对于那些很少看到圆角的访问者。(杰伊说它看起来更干净,事实上,但他从来不喜欢圆角。)

images

***图 7-6。*在旧浏览器上看到的第 1 层样式

让我们回到这个菜单的样式。清单 7-3 显示了父元素ul的 CSS:

***清单 7-3。*父 UL 的 CSS

`.navMainUL {   display: block;   min-height: 31px;   padding: 0 5px 0 8px;   border: 1px solid #cdbec4;   width: 100%;   margin-right: 5px;   -webkit-box-sizing: border-box;   -moz-box-sizing: border-box;   box-sizing: border-box;   -webkit-box-shadow: 2px 2px 2px #999999;   -moz-box-shadow: 2px 2px 2px #999999;   box-shadow: 2px 2px 2px #999999;   -webkit-border-radius: 10px;   -moz-border-radius: 10px;   border-radius: 10px;   -moz-background-clip: padding;   -webkit-background-clip: padding-box;   background-clip: padding-box;   z-index: 20;   position: relative;   -moz-background-clip: padding;   -webkit-background-clip: padding-box;   background-clip: padding-box;   /* Background */

  background: #fefefe;   background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZlZmVmZSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEz JSIgc3RvcC1jb2xvcj0iI2ZiZjlmYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjQ4JSIgc3RvcC1j b2xvcj0iI2VjZTVlOCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUyJSIgc3RvcC1jb2xvcj0iI2Rh ZDNkNiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjU4JSIgc3RvcC1jb2xvcj0iI2RhZDNkNyIgc3Rv cC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNlOGU4ZTgiIHN0b3Atb3BhY2l0 eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmls bD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);   background: -moz-linear-gradient(top, #fefefe 0%, #fbf9fa 13%, #ece5e8 48%, #dad3d6 52%, #dad3d7 58%, #e8e8e8 100%);   background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fefefe), color-stop(13%, #fbf9fa), color-stop(48%, #ece5e8), color-stop(52%, #dad3d6), color-stop(58%, #dad3d7), color-stop(100%, #e8e8e8));   background: -webkit-linear-gradient(top, #fefefe 0%, #fbf9fa 13%, #ece5e8 48%, #dad3d6 52%, #dad3d7 58%, #e8e8e8 100%);   background: -o-linear-gradient(top, #fefefe 0%, #fbf9fa 13%, #ece5e8 48%, #dad3d6 52%, #dad3d7 58%, #e8e8e8 100%);   background: -ms-linear-gradient(top, #fefefe 0%, #fbf9fa 13%, #ece5e8 48%, #dad3d6 52%, #dad3d7 58%, #e8e8e8 100%);   background: linear-gradient(top, #fefefe 0%, #fbf9fa 13%, #ece5e8 48%, #dad3d6 52%, #dad3d7 58%, #e8e8e8 100%);   filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fefefe', endColorstr='#e8e8e8', GradientType=0); }`

这里有很多 CSS,但大部分是不同的供应商前缀(例如–moz,-webkit 等。).没有必要解释所有的 CSS,因为它是非常基本的。然而,回顾一下 CSS3 的一些优点会有所帮助。具体来说,我们应该解释一下你可能会挠头的那个古怪的 base64 背景图像。

images 注意微软过滤器会导致性能非常差。如果没有它们也可以,那就跳过它们。我们在这里包括它们是为了支持 Internet Explorer 并提供一个完整的示例。

盒子尺寸

自浏览器早期以来,框的大小就一直是争论的焦点(这个问题可以追溯到页面布局软件的早期)。基本问题是如何处理填充、边距和边框。

假设您指定了一个宽度为 100 像素、填充为 20 像素、边距为 20 像素的 div 元素。(为了简单起见,我们将边框设置为 0。)得到的元素实际上会有多宽?嗯,这取决于谁实现了箱子尺寸。(如果有什么让开发人员抓狂的话,那就是“视情况而定”,这总是意味着他们必须为多种条件编码。)微软的一些人认为这个框应该是 140 像素宽:20 像素用于左边距,100 像素用于框本身,20 像素用于右边距。在这种情况下,填充位于框内,因此内容区域为 60 像素宽。这个规范和大多数实现它的人都说这个盒子应该是 180 像素宽。在该实现中,边距和填充都在框外,留下 100 像素宽的内容区域。

哪一种看起来是正确的,很大程度上是个人喜好的问题,许多人都试图证明这种或那种方法是正确的。严格来说,微软的实现是一个 bug。然而,他们的实现对很多人来说是有意义的,包括迈克尔。所以整个问题引起了一些争议。

更有趣的是(从某种众所周知的诅咒的意义上来说),不同版本的 Internet Explorer 以不同的方式实现框大小调整。在版本 6 之前,Internet Explorer 通过填充值缩小了内容区域。从版本 6 开始,Internet Explorer 将在其符合标准的模式下遵守规范(将填充放在内容区域之外),但在其古怪的模式下不遵守规范。因为当存在有效的文档类型时,Internet Explorer 进入标准兼容模式,所以指定有效的文档类型是控制这种行为的方法。另外,Mac 版的 Internet Explorer 从未缩小过内容区。

作者注迈克尔说:“就我个人而言,虽然这么说让我很痛苦,但我实际上同意 IE 版本的盒子模型的工作方式。对我来说,当你试图使用现实世界的类比时更有意义。如果我有一个装运箱,并添加了填充物,箱子本身不会变大。里面的空间变小了。”

Jay 更喜欢坚持 glue 这样的标准,但是这次他让 Mike 按照 Mike 的方式来做,因为 Jay 认为内容区域改变宽度是不好的。

我们确实同意,如果你正在做移动 web 开发,或者可以依赖拥有支持 CSS3 的浏览器的访问者,那么box-sizing属性会很方便。

我们选择使用一个box-sizing属性,这样我们可以设置 100%的宽度,并且仍然以像素为单位设置填充。这很方便,因为我们不希望填充因为在不同的显示设备上调整大小而伸缩或“自我调整”。

***清单 7-4。*设定定箱模型

box-sizing: border-box;

标准 CSS3 糖果

当然,我们使用了圆角和阴影。我们不会深入细节,因为代码本身读起来很好。此外,很有可能你已经对这些特殊的特性听得够多了。

渐变

如果你说这是一段冗长的 CSS 来描述ul元素的背景,我们同意你的观点。好消息是我们不需要写这些,你也不需要。ColorZilla 的优秀人员为您带来了 Firefox 的颜色选择器插件,他们也制作了可能是我们见过的最酷的 CSS web 应用之一。它被恰当地称为终极 CSS 渐变生成器。你可以在 www.colorzilla.com/gradient-ed…

本质上,它是一个非常强大的(“终极”非常接近)渐变生成器,输出一段相当防弹的代码,以确保您的渐变要么显示良好,要么逐步后退。一切为了低价,低价的免费!让我们来看看他们界面中的渐变,如图图 7-7 所示。

images

***图 7-7。*终极 CSS 渐变生成器

请注意,在“预置”面板中,您可以从许多不同的起点中进行选择。你也可以一头扎进去,忽略预设,从头开始酝酿你自己的渐变。

预设面板下方是神奇的地方。您可以通过输入名称并点击保存来保存您的渐变。如果你以后需要修改你的作品,这很方便。渐变的实际输入在保存功能下面。

images

***图 7-8。*渐变选择器

在图 7-8 的顶部,两个黑框构成了不透明度选择器。对于这个例子,我们将使用 100%的不透明度(这就是为什么手柄是纯黑色),但你可以选择用不透明度做一些真正有趣的组合。我们鼓励你去尝试。底部的一排方框是定义颜色的地方,也是“停止”的地方。以百分比定义的停止点基本上告诉工具颜色可能在哪里变化。然后,该工具负责计算出在停止点之间映射的颜色,以创建平滑的渐变。您可以通过双击方块来更改颜色,并通过单击左侧或右侧的相邻区域来进行更多停留。您也可以更改颜色,方法是点按其中一个颜色框,然后点按其下方“停靠点”面板中的颜色框。这有助于选择颜色的替代方法,因为双击时很容易不小心移动颜色框。

图 7-9 显示了我们选择的颜色。

images

***图 7-9。*渐变颜色

在“色阶”面板下面的“调整”面板中,您将有一些额外的选项来进行全面的色调/饱和度更改,并反转您的颜色顺序。

“预置”面板的右边是预览面板,您可以在这里看到渐变的形状。预览栏下是方向选项:垂直、水平、对角线和径向。大小选项只是预览的大小;它不会影响输出的 CSS。旁边是 IE 复选框,可以让你看到用两种颜色表示的渐变(这是前面提到的回退部分)。同样,这个复选框不会影响代码;这只是另一种预习方式。

CSS 窗格包含输出。有几个选项,包括你想要的颜色格式(十六进制,rgba,hsl 等。).您可以选择在代码中留下注释(我们通常在这里保存字节并关闭它们)。您也可以使用 IE9 支持复选框。在支持 HTML5 和相关技术方面,IE9 已经从早期版本走了很长的路。然而,一些差距仍然存在,梯度(至少复杂的梯度,如本例)是其中之一。您仍然可以使用渐变(通过指定filter: progid:DXImageTransform.Microsoft.gradient),但是它只支持两种颜色。这个工具的一个强大功能是,它可以创建一个 base64 编码的 SVG 来匹配正在定义的渐变。这有多酷?不过,要做到这一点,您需要禁用 IE9 的过滤器选项。该工具为此提供了代码,如清单 7-5 所示。

***清单 7-5。*关闭 IE9 的过滤器

`<!--[if gte IE 9]>   

     .gradient {        filter: none;     }    `

这是我们的老朋友,条件语句。如果浏览器是 IE9,该语句禁用过滤器。从那里开始,你只需要将gradient类添加到适用的元素中,如前面的清单 7-3 所示。

除了一长串的浏览器前缀外,它还支持这些浏览器的不同版本。如果所有这些都失败了,它将定义一个与滑块中的第一种颜色相匹配的平面背景颜色。

接下来,ul元素中有li元素;它们构成了第 1 层和第 2 层导航元素的容器。图 7-10 提醒你它的样子。

images

***图 7-10。*现代浏览器中的主菜单

清单 7-6 显示了一个定义菜单项的 li 元素

***清单 7-6。*一个定义菜单项的李元素

`

  •   Computer Hardware   

  • `

    清单 7-7 显示了样式化一个菜单项的 CSS。

    ***清单 7-7。*样式化菜单项的 CSS

    .nmLI {   border-right: 1px solid white;   border-left: 1px solid #ccc;   list-style: none;   text-align: center;   position: relative;   background: rgba(0, 0, 0, 0);   display: block;   float: left;   padding: 0px;   min-height: 31px; } .nmLI.first {   border-left: none; } .nmLI.last {   border-right-color: #ccc;   border-left-color: #ccc; } .nmLI:hover {   background: #a29da0; } .nmLI:hover:before {   content: "";   background: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAjCAMAAACEhlvCAAAAGXRFWHRT b2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+dpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0 YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAg ICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4g PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4 bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUu Y29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNl UmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wOkNyZWF0ZURhdGU9IjIw MTItMDUtMDFUMDA6NDk6MTgtMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDEyLTA1LTAxVDA2OjI2OjA3LTA1OjAwIiB4bXA6 TWV0YWRhdGFEYXRlPSIyMDEyLTA1LTAxVDA2OjI2OjA3LTA1OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgeG1wTU06SW5z dGFuY2VJRD0ieG1wLmlpZDpGODBFRTI0RDhCNzcxMUUxQjc2RUI3REExQzg1RUEyMSIgeG1wTU06RG9jdW1lbnRJRD0ieG1w LmRpZDpGODBFRTI0RThCNzcxMUUxQjc2RUI3REExQzg1RUEyMSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5j ZUlEPSJ4bXAuaWlkOkY4MEVFMjRCOEI3NzExRTFCNzZFQjdEQTFDODVFQTIxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlk OkY4MEVFMjRDOEI3NzExRTFCNzZFQjdEQTFDODVFQTIxIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4 bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+tVKbSQAAABhQTFRFmJSXb2ttgX1/j4uNk4+ShYGDcm5wnpmckwoARwAAABdJ REFUeNpiYGRiZmBgY2VhHxIEQIABAPz1AvMeVbYrAAAAAElFTkSuQmCC);   display: block;   position: absolute;   width: 4px;   height: 33px;   left: 0;   top: 0; }

    这里唯一棘手的是最后一个规则集.nmLI:hover:before,我们将在下一个规则集中讨论。在我们继续之前,我们认为你应该看看菜单的样子。当然,您也可以访问我们的示例网站([clikz.us](http://clikz.us))来看看它的运行情况。图 7-11 显示了游戏子菜单。

    images

    ***图 7-11。*游戏子菜单

    :之前和之后:伪类

    还记得:before伪类吗?为了省去你找它的麻烦,清单 7-8 重复了那个类。

    清单 7-8。:before伪级

    .nmLI:hover:before {   content: "";   background: url(data:image/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAAjCAMAAACEhlvCAAAAGXRFWHRT b2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+dpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0 YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAg ICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4g PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4 bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUu Y29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNl UmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wOkNyZWF0ZURhdGU9IjIw MTItMDUtMDFUMDA6NDk6MTgtMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDEyLTA1LTAxVDA2OjI2OjA3LTA1OjAwIiB4bXA6 TWV0YWRhdGFEYXRlPSIyMDEyLTA1LTAxVDA2OjI2OjA3LTA1OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgeG1wTU06SW5z dGFuY2VJRD0ieG1wLmlpZDpGODBFRTI0RDhCNzcxMUUxQjc2RUI3REExQzg1RUEyMSIgeG1wTU06RG9jdW1lbnRJRD0ieG1w LmRpZDpGODBFRTI0RThCNzcxMUUxQjc2RUI3REExQzg1RUEyMSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5j ZUlEPSJ4bXAuaWlkOkY4MEVFMjRCOEI3NzExRTFCNzZFQjdEQTFDODVFQTIxIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlk OkY4MEVFMjRDOEI3NzExRTFCNzZFQjdEQTFDODVFQTIxIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4 bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+tVKbSQAAABhQTFRFmJSXb2ttgX1/j4uNk4+ShYGDcm5wnpmckwoARwAAABdJ REFUeNpiYGRiZmBgY2VhHxIEQIABAPz1AvMeVbYrAAAAAElFTkSuQmCC);   display: block;   position: absolute;   width: 4px;   height: 33px;   left: 0;   top: 0; }

    这段代码可能看起来很奇怪,因为选择器中的:before伪类和看起来很奇怪的代码块。先说一下:before:after伪类。这个想法一开始可能看起来有点奇怪,但是一旦你习惯了这些伪类,它们可能会改变游戏规则。

    首先,让我们考虑浏览器支持。以下浏览器支持:before:after伪类:

    • Firefox 3.5+(3.5 之前的版本有部分支持)
    • Safari 1.3 以上版本
    • Chrome:所有版本
    • 歌剧:6+
    • IE: 9+(部分支持 IE8)

    因此,不要将这些伪类用于任务关键型代码,但是它们对于渐进式增强非常有用。事实上,由于浏览器支持有限,我们通常不使用这些伪类。

    :before:after伪类都在 DOM 中创建了另一个可以样式化的元素。此外,还可以添加文字。你可以用它来添加美元符号或时髦的引号或任何一千种其他东西。在这种情况下,让我们来一点演示技巧。在菜单中,当访问者的鼠标悬停在第 1 层li元素上时,让我们显示下拉菜单并更改li元素的背景。为了给人一种有深度的感觉,让我们在li元素的左边添加一个小阴影,让它看起来稍微有点凹陷。如果它没有出现在旧的浏览器上,那也没关系,因为它只是养眼而已。

    这是渐进增强的一个很好的例子。我们创造了一种适用于所有访问者的体验,使用功能更强大的浏览器的访问者可以获得与他们的浏览器功能相匹配的体验,因此会更好一点。

    使用 Base64 编码

    现在让我们来谈谈背景图片和时髦的代码。我们希望展示如何在 CSS 中嵌入图像的实际代码,而不是提供图像的路径。生成看似随机的字符块的技术称为 base64 编码。这是一种将图像存储为字符块的方式。(如果你打开一个图像文件,你会看到它也只是一个这样或那样的字符块,取决于图像;base64 编码在您的代码中创建了一个类似的构造。)

    使用 base64 编码可能很棘手,因为你必须了解缓存。如果您将 base64 编码的图像添加到 HTML 中,该图像将不会被缓存。因此,如果再次需要该图像,必须将它添加到下一个需要它出现的页面。那是一种痛苦。相反,应该将 base64 编码的图像放在 CSS 中,因为 CSS 会被缓存。

    即使您可以将它们放在 CSS 中,稍后从缓存中获取它们,处理引用的图像通常更容易,因为它们更容易维护,并且不会弄乱您的代码。不过,在这种情况下,通过不让 HTTP 请求获取该图像,可以从站点中挤出一点点性能。不过,最主要的是,让我们把它放在这里进行演示。

    另一个限制是 IE7 和早期版本不支持 base64 编码。在这个例子中,因为我们知道 IE7 不会支持:before伪类,所以我们不用担心。在我们已经使用这种技术并不得不考虑 IE7 使用的网站中,我们让服务器端控制器对图像进行动态编码,或者如果访问者的浏览器不能处理它,就用路径替换 base64 编码(我们在第二章中讨论了使用特征检测的基本思想)。对支持 base64 编码的浏览器使用 base64 编码是检测访问者浏览器功能并为其编码的另一个例子。

    清单 7-9 显示了第一层li元素中的锚标签。

    ***清单 7-9。*一级主播风格

    .nmA {   display: block;   line-height: 110%;   font-size: 15px;   color: #606060;   text-decoration: none;   background-color: rgba(0, 0, 0, 0); padding: 7px 15px 3px; } .nmA:visited {   color: #606060; } .nmA:hover {   color: #606060;   text-decoration: none; }  nmLI:hover .nmA {   text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.4); }

    这里唯一需要注意的是最后一个规则集:.nmLI:hover .nmA{}

    我们指定,当.nmLI被悬停时,li元素内的锚(由.nmA标识符标识)应该有不同的样式。在这种情况下,我们指定在文本上放置阴影,以便它与下拉部分中的其他链接相匹配。如果 JavaScript 被禁用,我们将使用相同的技术来激活下拉菜单。稍后会有更多内容。

    现在让我们看看下拉菜单;首先,我们需要实际的下拉容器。这是一个 div,有一个类nmSlideout zeroHeight位于我们的第 1 层锚标签旁边。清单 7-10 展示了 CSS。

    ***清单 7-10。*下拉容器的 CSS

    `.nmSlideout {   overflow: hidden;   min-width: 100%;

      -webkit-transition: all 0.25s ease-in-out;   -moz-transition: all 0.25s ease-in-out;   -ms-transition: all 0.25s ease-in-out;   -o-transition: all 0.25s ease-in-out;   transition: all 0.25s ease-in-out;

      position: absolute;   z-index: 11;   -webkit-border-top-right-radius: 0;   -webkit-border-bottom-right-radius: 5px;   -webkit-border-bottom-left-radius: 5px;   -webkit-border-top-left-radius: 0;   -moz-border-radius-topright: 0;   -moz-border-radius-bottomright: 5px;   -moz-border-radius-bottomleft: 5px;   -moz-border-radius-topleft: 0;   border-top-right-radius: 0;   border-bottom-right-radius: 5px;   border-bottom-left-radius: 5px;   border-top-left-radius: 0;   -moz-background-clip: padding;   -webkit-background-clip: padding-box;   background-clip: padding-box;   background-color: #a29da0;   background: -webkit-gradient(linear, left top, left bottom, from(#a29da0), to(#898587));   background: -webkit-linear-gradient(top, #a29da0, #898587);   background: -moz-linear-gradient(top, #a29da0, #898587);   background: -ms-linear-gradient(top, #a29da0, #898587);   background: -o-linear-gradient(top, #a29da0, #898587);   -webkit-box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.4);   -moz-box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.4);   box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.4);   top: 100%;   min-width: 170px; }`

    除了大量带有供应商前缀的防弹功能,我们还有一些其他有趣的代码。最值得注意的是下面一行:

    transition: all 0.25s ease-in-out;

    当 CSS3 可用时,该行提供动画。翻译成英语,它基本上是说,“如果某些属性发生了变化,让我们来制作动画。”清单 7-11 展示了如何让它运行。

    ***清单 7-11。*CSS 过渡的一般模式

    transition: [what properties to animate; i.e., width, height, etc.] [how long should the animation take] [what kind of easing should be used];

    所以在这个例子中,所有的方面(冒号后的关键字all)都被动画化了。下一个值指定动画持续时间,即原始状态和您设置的结束状态之间的时间。在这种情况下,那就是四分之一秒(0.25s)。然后是缓动说明符,这个说明符有点复杂。真的是曲线加速率。所以如果你用一个linear的值,它将是一个变化的恒定速率,但如果你用ease-in-out,它将在开始和结束时加速得更快,而中间会慢一点。ease-in-out的设定倾向于让动画看起来更真实;当事物在现实生活中运动时,它们通常不会以同样精确的速度运动。

    你可能会想,“我如何告诉它改变。”问得好。有几种方法;最简单的是使用伪类,比如:hover。现在你可以在.nmSlideout:hover中定义一种不同的颜色,动画就会自动出现。那是一些不错的魔术。或者,在我们的例子中,您可以使用 JavaScript 来更改类或 CSS 属性,转换将由此触发。

    表 7-1 显示了许多可以设置动画的属性。

    images

    images

    images 作者注是的,这是一场漫长的战役,我们还没有描述完我们的菜单是如何运作的。请容忍我们。如果这算是一种安慰的话,这也是一个很长的章节(重写了几次)。

    现在让我们来设计下拉列表中链接的样式。让我们再次使用一个包含锚标签的无序列表。清单 7-10 显示了ulli类的 CSS。

    清单 7-12。【CSS 给和李上课

    .nmUL-L2 {   text-align: left;   position: absolute;   bottom: 0;   display: block;   padding: 5px 10px 10px 10px;   float: left; } .nmLI-L2 {   padding: 1px 0; }

    那一点造型不含惊喜。然而,我们将在锚标签和:before伪类中遇到更多的转换乐趣。我们将使用这些技术来突出显示一个菜单项,如图图 7-12 所示。

    images

    ***图 7-12。*游戏菜单中突出显示了一个项目

    清单 7-13 显示了我们用来定义菜单高亮行为的代码。

    ***清单 7-13。*动画显示并高亮显示菜单项

    `.nmA-L2 {   color: white;   background: rgba(0, 0, 0, 0);   text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.4);   padding: 7px 5px 5px 18px;   display: block;   position: relative;   -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;   -webkit-border-radius: 10px;   -moz-border-radius: 10px;   border-radius: 10px;   -moz-background-clip: padding;   -webkit-background-clip: padding-box;   background-clip: padding-box;   border: 1px solid rgba(0, 0, 0, 0); } .nmA-L2:visited {   color: #ffffff; } .nmA-L2:hover {   background: rgba(0, 0, 0, 0.3);   text-decoration: none;   color: white;   border: 1px solid #c5bfc3; } .nmA-L2:before {   content: "::";   /background: url(../img/menu-arrow.png) no-repeat;/

      width: 15px;   height: 16px;   font-family: times, serif;   display: inline-block;   color: #828282;   font-weight: 700;   position: absolute;   text-shadow: -1px 0 #f3f3f3, 0 1px #f3f3f3, 1px 0 #f3f3f3, 0 -1px #f3f3f3;   top: 6px;   left: 5px; }`

    您可能注意到了.nmA-L2规则集中的转换。当我们将鼠标悬停在第 2 层链接上时,该规则集处理轻微的转换,这样我们可以看到一个框在它们周围淡入。我们通过使用以下代码行设置初始背景来实现这一点:

    background: rgba(0, 0, 0, 0);

    该值为突出显示的菜单项设置红色、绿色、蓝色和 alpha。(我们知道你知道这一点,但我们必须告诉你这一点,以便我们可以告诉你下一点。)阿尔法部分是我们最感兴趣的。将初始值设置为 0(零)会使其完全透明。然后我们在.nmA-L2:hover中用下面一行设置悬停状态:

    background: rgba(0, 0, 0, 0.3);

    该设置使其成为 30%不透明度的黑盒。有趣的东西。

    您还可以看到,我们使用了:before伪类,并且做了一些我们说不经常做的事情:用 CSS 插入文本。然而,我们这次这样做有一个很好的理由:我们可以得到一个简单的图标,它比用图像得到的图标表现得更好。为此,我们使用::(两个冒号字符),然后给菜单项一个轮廓。清单 7-14 显示了添加两个冒号字符的行。

    ***清单 7-14。*用 CSS 添加两个冒号

    nmA-L2:before {   content: "::"; /* The insertion of colon characters   text-shadow: -1px 0 #f3f3f3, 0 1px #f3f3f3, 1px 0 #f3f3f3, 0 -1px #f3f3f3; }

    images 提示正如我们在第一章中提到的,实用主义战胜了纯粹主义。已经说过我们从不通过 CSS 插入文本,我们可以拒绝这样做。然后,我们必须使用一个图像来创建这个小图标。这将是一个错误,因为这将迫使我们管理另一项素材,从而影响业绩。如果打破规则能带来好处,那就打破规则。当然,诀窍是知道何时打破规则。

    获得课文的大纲有点棘手。由于文本上没有轮廓的概念,我们可以通过使用一系列基本上实心的文本阴影来欺骗它。清单 7-15 显示了定义文本阴影的一般模式。

    ***清单 7-15。*定义文本阴影的一般模式

    Text-shadow: [horizontal offset] [vertical offset] [blur] [color];

    因为我们没有定义模糊度,所以假设模糊度为 0。在这种情况下,模糊度为 0 会使它看起来像一条实线。因为我们确保每个方向都有一个实阴影,阴影重叠,最终结果是一条实线。巧妙的把戏,不是吗?

    然后剩下的就是绝对地定位它,把它移到左边,使用左边的填充来确保按钮文本不与它相交。这就是我们的技术:简单有效。

    下拉效果

    既然样式已经就位,剩下的就是以一种包含渐进增强核心价值的方式来制作下拉菜单的动画。在这里,您将不得不稍微依赖一下 JavaScript。如果使用.nmSlideout标识符手动设置每个元素的高度,使每个元素都与其内容一样高,那么在 CSS3 中您实际上可以完成这一切。如果所有这些值都是手动设置的,下拉菜单的动画可以用清单 7-16 中的简单规则来定义。

    清单 7-16。 CSS3 动画下拉方式

    .nmLI:hover .nmSlideout {         height: some number px; }

    那会工作得很好,但是会有更多的前期编码工作要做。更糟糕的是,如果内容发生了变化,您必须记得重新测量新的内容。最糟糕的是,如果维护内容的人和维护设计的人不是同一批人,那么你的页面肯定会出现明显的错误。因此,采取简单(也是聪明)的方法,让 JavaScript 来帮忙。然而,最好采用一种对 JavaScript 依赖很少的方法,这样性能就很好(记住计算也总是有成本的)。清单 7-17 显示了代码。

    ***清单 7-17。*设置下拉框高度的 JavaScript】

    var nmLICol = $(".nmLI"), slideoutCol = $(".nmSlideout"); $(".nmSlideout").each(function() {   var t = $(this), level2Nav = $(".nmUL-L2", t);   t.css({     "height" : level2Nav.outerHeight() + "px"   }).attr("data-height", level2Nav.outerHeight() + "px"); })

    这段代码抓取所有标识符为.nmSlideout的元素;然后,对于每一个元素,它找出其中的ul元素有多高,并将标识符为.nmSlideout的元素的样式设置为该高度。最后,它将data-height属性的值设置为相同的像素高度(+ "px")。稍后将需要该值,所以我们在这里设置它。

    现在我们来看一下.nmSlideout的高度应该是多少,以容纳内部内容,并使其显式化。然而,你仍然需要一个高度来过渡。唯一的问题是它的高度不再是 0,因为它的内联样式已经被设置为一个新的高度。这里有一点小技巧。

    你可能注意到了我们的.nmSlideout元素的第二个类:zeroHeight。清单 7-18 显示了那个类。

    ***清单 7-18。*美国。零高度等级

    .zeroHeight {   height: 0 !important;   -webkit-box-shadow: 0 0 0 rgba(0, 0, 0, 0) !important;   -moz-box-shadow: 0 0 0 rgba(0, 0, 0, 0) !important;   box-shadow: 0 0 0 rgba(0, 0, 0, 0) !important; }

    这个类中最重要的事情是它将.nmSlideout元素的高度设置为 0(零),并添加了一个!important声明,因此它胜过任何没有!important声明的规则。所以又回到了零的高度。要制作动画,使用 jQuery 移除mouseenter事件上的zeroHeight类,并将其添加到mouseleave事件上。这将使浏览器遵循之前用 JavaScript 设置的高度。当它向后设置时,它会自动回到零高度。清单 7-19 显示了在mouseentermouseleave事件上设置类的 JavaScript 代码。

    ***清单 7-19。*在 mouseenter 和 mouseleave 事件上改变类的 JavaScript】

    `nmLICol.each(function() {   var t = (this);   t.hover(function() {     var slideout = (".nmSlideout", t);     setTimeout(function() {       slideout.removeClass("zeroHeight");     }, 0);   }, function() {     var slideout = t.find(".nmSlideout");

        slideout.addClass("zeroHeight");   }) })`

    第 1 层li元素已经被收集到一个集合中,然后为每个元素添加一个悬停事件。这个jQuery .hover()带两个函数:第一个是 hover enter 函数,第二个是 hover leave 函数。(顺便说一下,这些函数是匿名的,在运行时由它们的签名来标识——也就是参数的数量。)它添加了mouseentermouseleave事件处理程序。延迟为零的setTimeout函数是让 Firefox 正常运行所必需的。

    现在我们有了现代浏览器的导航,使用 JavaScript,在大多数机器上应该在 20 毫秒内运行,并利用 CSS3 处理动画。对许多游客来说,它又快又平稳。但是非 CSS3 浏览器呢?为此,我们依靠 Modernizr 来确定 CSS3 是否可用。当它不可用时,依赖 jQuery 及其动画功能。正如我们在第二章中提到的,我们坚信使用特征检测来帮助塑造提供给每个访问者的体验。清单 7-20 展示了如何检测浏览器处理 CSS3 的能力,以及当浏览器不能处理 CSS3 时该怎么做。

    ***清单 7-20。*非 CSS3 浏览器的特征检测和动画

    nmLICol.each(function() {   var t = $(this);   t.hover(function() {     var slideout = $(".nmSlideout", t);     setTimeout(function() {       slideout.removeClass("zeroHeight")     }, 0);     //Add fallback if CSS3 animations are not available     if(!Modernizr.cssanimations) {       slideout.css("height", 0);       slideout.stop().animate({         "height" : slideout.attr("data-height")       }, 400);     }   }, function() {     var slideout = t.find(".nmSlideout");     if(!Modernizr.cssanimations) {       slideout.stop().animate({         "height" : 0       }, 400, function() {         slideout.addClass("zeroHeight");       });     } else {       slideout.addClass("zeroHeight");     }   }) })

    内置了一些逻辑来检测 CSS3 动画是否可用,如果不可用,就使用 jQuery 的animate函数。我们依靠 Modernizr 来检测 CSS3 动画特性是否在!Modernizr.cssanimations中可用。如果条件评估为假,我们通过在零高度和.nmSlideout显示其内容所需的高度之间制作动画来模仿 CSS3 的行为。我们从清单 7-15 中第一次运行脚本时设置的data-height属性中获取高度。除了将mouseleave事件的高度设置为零,我们还需要将zeroHeight类设置回来。如果我们已经将高度设置为零,为什么还要这样做呢?因为我们需要 CSS 使默认状态看起来像有一个透明的边框,这样当动画发生时元素就不会移动。

    你差不多完成了。(感谢大家的包容。正如我们之前所写的,这对我们来说也是一个漫长的篇章。)现在我们有了一个菜单,可以在支持 CSS3 的浏览器和不支持 CSS3 的浏览器上工作。现在是帽子戏法。如果你的访问者没有 JavaScript 怎么办?没问题。由于 HTML 的结构方式,很容易使用一种既定的方法来显示和隐藏悬停时的下拉菜单。同样,让我们使用 Modernizr,但是依赖于当访问者关闭 JavaScript 时它不存在。这听起来可能很疯狂,但这是 Modernizr 的另一个很棒的特性:它在 HTML 标签中查找并删除了no-js类,插入了js类。您现在可以利用no-js在 HTML 标签中的事实,因为如果没有 JavaScript,Modernizr 就不能运行,因此不能删除no-js类。它微妙而有效——就像我们都喜欢的东西一样。清单 7-21 显示了我们菜单的.no-js类:

    ***清单 7-21。*菜单的无 js 类

    .no-js .zeroHeight {   height: auto ! important; } .no-js .nmSlideout {   display: none; } .no-js .nmLI:hover .nmSlideout {   display: block; }

    .nmLI:hover上,显示标识为.nmSlideout的元素。当悬停状态不再有效时,标识为.nmSlideout的元素被隐藏。

    搜索框

    最后但同样重要的是,有搜索框。考虑到大量的网络用户依靠搜索来寻找东西,你需要提供这种能力,并使它容易找到。对于这个站点,让我们设计站点搜索框的样式,使其位于导航的右侧。清单 7-22 展示了如何去做。

    ***清单 7-22。*设计搜索框

    .searchWrap {   text-align: right;   border: 0;   padding: 0 0 0 10px;   float: right; } .searchWrap:hover {   background: none; } .searchWrap:hover:before {   background: none; } .searchInput {   border: 1px solid #ccc;   display: inline-block;   position: relative;   margin: 4px 0 0 0;   height: 14px;   padding: 5px 4px 2px 5px;   width: 150px;   -webkit-border-top-right-radius: 0;   -webkit-border-bottom-right-radius: 0;   -webkit-border-bottom-left-radius: 5px;   -webkit-border-top-left-radius: 5px;   -moz-border-radius-topright: 0;   -moz-border-radius-bottomright: 0;   -moz-border-radius-bottomleft: 5px;   -moz-border-radius-topleft: 5px;   border-top-right-radius: 0;   border-bottom-right-radius: 0;   border-bottom-left-radius: 5px;   border-top-left-radius: 5px;   -moz-background-clip: padding;   -webkit-background-clip: padding-box;   background-clip: padding-box;   -webkit-box-shadow: inset 2px 2px 2px #999999;   -moz-box-shadow: inset 2px 2px 2px #999999;   box-shadow: inset 2px 2px 2px #999999; } .searchBtn {   -webkit-border-top-right-radius: 7px;   -webkit-border-bottom-right-radius: 7px;   -webkit-border-bottom-left-radius: 0;   -webkit-border-top-left-radius: 0;   -moz-border-radius-topright: 7px;   -moz-border-radius-bottomright: 7px;   -moz-border-radius-bottomleft: 0;   -moz-border-radius-topleft: 0;   border-top-right-radius: 7px;   border-bottom-right-radius: 7px;   border-bottom-left-radius: 0;   border-top-left-radius: 0;   -moz-background-clip: padding;   -webkit-background-clip: padding-box;   background-clip: padding-box;   border: 1px solid #ccc;   top: -2px;   position: relative;   color: white;   display: inline-block;   padding: 3px;   text-shadow: 0 0 4px rgba(0, 0, 0, 0.3); } .searchBtn:hover {   background: #c62125; }

    如您所见,.searchWrap类定位搜索框,而其他.search类设计搜索文本框及其相关按钮的样式。我们确信您以前见过这种结构,所以我们不会详细讨论这个话题。

    总结

    虽然一章的长度并不真正重要(尽管我们确实试图避免超出普通人一个午餐小时所能消化的范围),但它揭示了创建一个有效的导航范例需要相当多的代码。它不需要太多的 HTML,但是需要一些复杂的 CSS 和(在较小程度上)JavaScript。

    在这一章中,我们想留给你的主要思想是,使用渐进式改进来确保你的访问者获得最佳的浏览体验。我们从“完全增强”的一端开始,为浏览器支持 CSS3 的访问者编写规则。然后,我们讨论了不能处理 CSS3 但支持 JavaScript 的浏览器。最后,我们为那些浏览器不能使用 JavaScript 的访问者提供了一个解决方案。

    我们强烈建议你将这个想法应用到你自己的导航元素和你开发的网站的其他方面,只要这样的技术对你的访问者有好处。