HTML5-性能高级教程-全-
HTML5 性能高级教程(全)
一、简介
不久前,在面试职位空缺的候选人时,我们发现我们的开发伙伴在性能和可伸缩性领域存在一些明显的知识差距。虽然许多开发人员完全精通他们选择的服务器端语言,但他们对 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】
`在这个例子中,我们一次设置一个元素的样式。首先,脚本将背景颜色设置为红色(强制重绘),然后将宽度设置为 200px(强制第二次重绘),然后将文本颜色设置为白色(强制第三次重绘)。虽然您仍然可以以多种方式使用 JavaScript 来将这些样式更改合并到一个调用中,但是更容易且更易于维护的方式是使用 JavaScript 来设置包含所有这些属性的 CSS 类。这样做会将所有样式更改合并到一次重绘中。清单 2-2 展示了一个只强制一次重绘的重造型的例子。
清单 2-2。创建单个重绘事件的 JavaScript】
`最后,您应该将 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
属性
现在,网站的访问者在承诺做某件事之前会得到一个提示,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 性能优化的信息来源中,我们发现以下是最有用的:
总结
这一章介绍了一些优化网站访问者页面加载时间的方法。以下是你可以快速做出的简单改变(如果你还没有的话):
- 将 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。包含媒体查询的样式元素
``
在清单 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.
`
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 中声明高度或宽度。发生的情况是,图像以其原始大小显示,然后如果容器小于原始图像大小,则缩小到其包含元素的宽度。就像魔法一样。
注意: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 所示。
图 4-5。带水槽的 12 列网格
可以在元素上设置完全适合列网格的预定义类。这些类类似于“colOne”、“colTwo”到“colTwelve”。为了清楚起见,我们想指出“colOne”并不是指第一列。它引用一列宽的元素,该元素很可能在第 10 列或其他地方。类似地,列 2 引用两列(包括列之间的装订线)宽的元素,并且可能跨越任意两列。
然后,如果你想要一个占据整个页面宽度的 div,你可以创建一个类似于清单 4-6 中所示的元素。
清单 4-6。穿过整个网格的div
元素
<div class="colTwelve"></>
结果将类似于图 4-6 中的。
图 4-6。一个穿过网格中所有列的 div
类似地,如果你想要三个相等的列(它们之间有间距),你可以创建类似于清单 4-7 中所示的元素。
清单 4-7。网格中三个相等的列
`
`结果将类似于图 4-7。
图 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 确定的设计变更(当然,删除了所有公司和产品信息)。
图 5-1。设计变更
当 Mike 检查这些设计意图时,他不禁注意到它们都有相似的项目,只需要改变它们的布局。他很快意识到这是 CSS 的工作,而不是一堆单独的视图。
注意迈克想感谢妮可·沙利文,她也经历了类似的过程,当时她重组了脸书的内容,并发现大量内容都归入了几个模式。她对这个过程的描述(在网上很多地方都可以找到)极大地启发了 Mike 在这个类似问题上的工作。
Mike 发现了各种设计意图的相似之处。图 5-2 通过颜色编码显示了相似之处。
图 5-2。设计的相似性
如你所见,所有这些设计意图共享一个图像、一个标题和一个描述。此外,大多数人还分享定价信息和行动呼吁按钮。我们创建了一个包含所有元素(图像、标题、定价信息、描述和行动号召)的动态主产品堆栈,而不是创建不同的 HTML 片段来在我们的大型高带宽电子商务网站中重复使用。然后,我们在每个元素周围放置条件语句,这样我们就可以控制它是否在被推送到客户浏览器的 HTML 中呈现。我们在父元素中插入一个 CSS 类来实现我们的设计意图。我们对此并不过分严格。如果出现了不适合我们现有模式的新产品堆栈处理,我们总是可以选择创建另一个视图。也就是说,在一年半的时间里,我们被要求进行大量的更改和六次额外的处理,我们只需要创建一个额外的视图(对于一个可切换的功能,我们认为它超出了我们的产品堆栈视图的范围)。
让我们来看一些代码。清单 5-1 显示了产品堆栈背后的 HTML(的简化版本)。
清单 5-1。产品堆栈的 HTML】
`
![](img/image.jpg)
Title
LabelPrice
LabelPrice
LabelPrice
Description
- Bullets
虽然您可以复制和粘贴框架,并手动填充文本和图像路径,但更有效的方法是使用您选择的服务器端解决方案,并动态填充产品堆栈。在 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";
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 }
布尔逻辑让我们只发送我们特殊处理所必需的代码,这样我们就不会有发送不必要的代码然后用 CSS 隐藏它的开销。通过这种方法,我们在代码匹配的模式上获得了很大的灵活性。
最棒的是,通过简单地给我们的section element with the class: “productStackWrap”
添加一个类名,我们可以表达各种各样的设计意图。请记住,我们对每个 CSS 转换都使用相同的 HTML。
注意我们利用 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 显示了结果。
图 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 显示了结果。
图 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 显示了结果。
图 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 显示了结果。
图 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)
找到
图 6-1。我们正在建设的样本网站( clikz.us
)的
我们将通过首先创建一个页面模板来开始构建我们的示例网站。在。NET 世界中,这样的模板通常被称为母版页。我们也看到它被称为主布局。不管它叫什么,页面模板是我们构建网站的框架。
对于我们的示例站点,我们将创建一些设置各种选项的元素,并指定一个灵活的网格。在它们之间,这些项目构成了网站上所有页面的基础。
设置样板文件选项
首先,我们将主要依靠 ` `www.html5boilerplate.com`` 提供的工具来生成一个不错的 HTML5 起点。HTML5 样板网站提供了很多我们在本章中不会涉及的配置细节,包括 web 配置、构建脚本等等。由于我们在本书中关注的是 HTML 和 CSS,所以我们在示例站点中省略了这些特性。我们强烈建议您详细查看 HTML5 样板网站,寻找可能对您有用的内容。我们也要感谢保罗·爱尔兰、迪维娅·马年、施尚恩、马蒂亚斯·拜恩斯和尼古拉斯·加拉格尔(仅举几例)为我们提供了这个有用的工具。他们做了有益于所有网络开发者的伟大工作。
在我们分析代码之前,您可能会看到一个完整的样板页面。清单 6-1 显示了这样一个清单。
清单 6-1。一个完整的页面模板
`<!doctype html>
<head> <meta charset="utf-8">
HTML 元素周围的条件语句
通常(通常,我们怀疑),您需要识别您站点的个人访问者正在使用的浏览器,以便您可以向该访问者提供最好的体验。将 HTML 元素包装在条件语句中允许您这样做,尽管只适用于 Internet Explorer。
注意:我们在这一章中对 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 显示了基本操作符。
如清单 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 浏览器规则。
提示:微软有一个“兼容性视图列表”,这是微软的人认为需要用 IE7 引擎渲染的网站列表。要查看您的网站是否在该列表中,请访问以下网站:
ie9cvlist.ie.microsoft.com/ie9CompatViewList.xml
。
如果你想在兼容模式下测试你的站点,你可以通过 ie 浏览器中可用的开发者工具手动强制一个页面在兼容模式下显示(按下 F12 或者查看工具菜单)。您还可以使用兼容模式来测试跨浏览器代码。然而,不要单独依赖这种技术,因为它不是 100%可靠的;在各种浏览器中查看和测试页面仍然是无可替代的。图 6-2 显示了在 Internet Explorer 中可以找到浏览器模式和文档模式设置的地方。从这些列表中,您可以选择“兼容模式”
图 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">
注意:前面的元元素会导致验证问题。通常最好在您的中添加一行。设置兼容模式的 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,您将很难找到更好的加载方式。
提示: 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 所示的目录结构。
图 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 所示的基本布局。
图 6-4。我们的样本电子商务网站的基本布局
为了实现这种布局意图,我们必须将列元素包装在几个父元素中。清单 6-8 显示了这种关系。
清单 6-8。父元素与网格的关系
`
类别为container
的div
元素跨越了访问者浏览器的整个宽度。类别为row
的div
元素将是你的站点的外部宽度。如果访问者的浏览器宽度为 1200 像素或更大,网格的宽度为 1140 像素(1140 网格设置的最大值)。当使用较小分辨率的访问者到达您的站点时,分辨率低于 1140 时,它的宽度会缩小。具有类twelvecol
的div
元素保存你的站点内容。因为我们已经使用了最大的列数,这个div
元素的宽度将是row
div 宽度的 100%。这种排列可能会让您想起 HTML 表格结构,它不仅在结构上相似,在功能上也相似,因为列宽会根据可用宽度进行伸缩。container
div
对应table
元素,row
div
对应tr
元素,twelvecol
div
对应td
元素。
回到我们的示例电子商务站点,清单 6-9 显示了构成站点框架的 HTML 元素。
清单 6-9。我们示例电子商务网站的基础结构
`
如您所见,我们在 div 以外的元素上设置了容器、行和列类。为了保持清晰的意图,最好在块级元素上设置这些类。您还可以看到,我们已经向右边的列 div 添加了一个类last
。您需要对多列布局的最后一列执行此操作。它唯一的功能是去掉右边距,这样内容就不会意外换行。
我们现在已经有了响应式电子商务网站的基本结构。在我们用控件和内容填充完新结构后,我们将进一步调整平板电脑和移动设备的布局。
总结
在这一章中,我们介绍了使用模板页面作为网站上所有(或者至少是大部分)页面的基础。为了制作这样一个页面,我们引入了由[
html5boilerplate.com](http://html5boilerplate.com)
背后的优秀人员提供的优秀工具。
我们还介绍了 html5boilerplate.com 工具为我们创建的各种设置,包括
- Internet Explorer 中的条件语句。
- 设置字符集。
- 控制 IE 的兼容模式。
- 提示安装谷歌浏览器框架。
- 控制 iPhones 上的视窗。
- 正在加载 jQuery。
- 添加谷歌分析。
最后但同样重要的是,我们描述了我们将用于整个站点的网格系统。1140 CSS 网格在一个灵活的网格中做了我们需要的一切,甚至优雅地处理移动设备。
在本书的其余部分,我们不会涉及这些信息的大部分(除了可能在这里或那里顺便提及)。然而,当您检查我们的示例站点背后的代码时,您会在每个页面上看到这些特性。
七、导航
除非你只是制作一个页面,否则导航是你的网站的一个重要方面。因此,当涉及到网站成功的关键因素时,你真的应该考虑一下你的策略。除了有一个好的分类法,你还需要有一个清晰的方法让访问者在你的网站上找到和探索不同的产品。有时候你可能会被诱惑在你的界面中加入很多活力和重量,尤其是当你首先从设计开始的时候。但是,内容才是王道。因此,作为一个规则,你的导航不应该和你的内容争夺注意力。也就是说,你的导航应该易于查看,更重要的是,易于使用。
为了使导航方案真正易于使用,你应该支持不同类型的访问者行为。不同的人喜欢用不同的方式导航站点,你应该支持所有导航站点的常用方式,以免你赶走那些不能如他们所愿导航的用户。一本关于可用性的很棒的书,不要让我想到、、 1 、有一个类比:把你的站点想象成一个百货商店。一些购物者看着部门标志,然后在过道里走来走去,直到找到他们想要的商品。有些人会找到商店员工,询问商品在哪里。第一批购物者是使用菜单找到他们想要的内容的访问者。询问店员的购物者是那些会使用网站搜索的人。您应该为这两个群体提供机制。
进一步说,百货公司把过道里的一些商品放在端盖上,这样当游客走过时,他们可以向他们展示这些商品。您应该考虑通过侧面导航这样的设计元素来提供类似的导航形式,侧面导航包括在主导航中也可用的项目(通常是下拉菜单)。我们称这种导航为“第三级导航”,因为它是我们导航方案的第三级,前两级是菜单和搜索。
在深入菜单背后的代码之前,展示一下我们在本章剩余部分创建的菜单图像是有意义的。图 7-1 显示了菜单的大部分(比页面宽度要宽,但图中显示了大部分)。
图 7-1。菜单处于就绪状态
现在,让我们来看看菜单的一些扩展形式。图 7-2 显示了游戏子菜单的放大视图。
1 史蒂夫·克鲁格,别让我胡思乱想!网站可用性的常识方法(新骑士出版社,第二版,2005)
图 7-2。鼠标悬停时游戏响应菜单
菜单结构
创建菜单结构时,您希望定义菜单内容的 HTML
- 要有意义。
- 可读性强。
- 易于屏幕阅读器理解(或忽略)。
- 非常适合渐进的设计目标。
当我们说你希望菜单使用有意义的 HTML 时,我们的意思是你希望能够查看它的源代码,并看到各种元素(容器和内容)之间的关系。当我们说您想要可读的 HTML 时,我们的意思是您不希望它因为混乱或者使用难以理解的类名而变得难以理解。当我们说你想要一个易于屏幕阅读器理解的菜单结构时,我们的意思是你想要一个不会浪费视觉障碍访问者过多时间的菜单结构(这将在本章后面更详细地讨论)。最后,当我们说你想要符合渐进设计目标的 HTML 时,我们的意思是你想要的内容不会妨碍你为你的每个目标浏览器提供最好的显示。
我们可以从大量可能的菜单结构表示中进行选择,因为 HTML 为表达这种内容提供了相当大的灵活性。为了达到这些目标,嵌套无序列表是最有意义的。由于列表中列表的方法是菜单内容的自然映射,它很容易满足有意义和可读的目标。当我们听屏幕阅读器浏览菜单时,列表中列表结构提供了菜单最直接的听觉表现。最后,我们知道我们可以让列表中的列表结构为渐进的增强目标而工作,正如我们在本章的其余部分所演示的那样。
最后,嵌套列表方法的相对自然的结构应该尽可能地面向未来。可上网设备的数量继续飞速增长,所以没有办法知道你的网站什么时候会出现在某人的冰箱上(老实说,不要感到惊讶)。
清单 7-1 显示了基本的菜单结构。
注:后面的类名中
nm
代表navMain
。同样,L2
表示二级列表的组成部分。我们是节省字节的朋友,也是懒惰的打字员。
清单 7-1。基本菜单结构
`
`我们使用一个非常基本的结构,你会在许多导航方案中发现(当约定工作良好时,遵循约定是好的)。在没有样式或脚本的情况下,浏览器呈现这个结构,如图 7-3 所示。
图 7-3。造型前的无序列表
您可以看到菜单结构是可读的,并且表达了正确的嵌套。在这种情况下,2 级菜单项是父 1 级菜单项的子级。我们现在可以添加样式和功能来突出您的意图。不过,在开始设计样式之前,让我们先看看提供实际菜单的 HTML。
当您阅读下面的清单时,您可能会注意到围绕第 2 层导航的额外的div
元素。它的类名是nmSlideout zeroHeight
。额外的标记启用了滑出技巧(我们将用菜单元素的样式来介绍)。接下来,您可能会注意到,我们在第 1 层菜单的最后一个LI
元素(带有一个nmLI searchWrap
类)中添加了一个搜索框。
让我们花一点时间来讨论一些基于标准的开发人员的症结所在:在这个例子中(以及我们所有的 HTML 代码中)对类名的富有表现力的使用。我们采用了这种方法,因为它很适合我们,我们现在将解释这一点。
一方面,你可能会说,通过使用 CSS 嵌套,用更少的标记就可以为嵌套元素指定类名。例如,你可以用代替一个类为nmLI
的li
元素,而是通过声明它的父元素并如下钻取来得到那个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 。
图 7-4。一级造型
注意在谈论菜单的其余部分之前,我们要指出看不见的菜单项,由
class="visuallyhidden"
标识。该菜单项以及其中的链接允许使用屏幕阅读器的访问者跳过该菜单。如果你曾经因为听菜单而痛苦,你就能理解为什么这个选项对有视觉障碍的游客来说是友好的。因为适应所有用户是非常重要的,所以要注意为视觉能力不同于普通访问者的人提供这种便利。
设计菜单
现在我们已经详细描述了菜单的结构和它的选择器,让我们继续把嵌套列表变成一个菜单。我们将从设计父元素ul
开始。图 7-5 显示了期望的设计处理。
图 7-5。现代浏览器中的 UL 造型
我们在这种治疗中没有使用图像;换句话说,菜单完全是通过代码定义的。我们大量利用 CSS3 来获得大量额外的视觉冲击,所以你可能会合理地想,“IE8 呢?”这又引出了另一个问题,而且是一个大问题。并非所有的浏览器都需要显示完全相同的演示文稿。事实上,试图让所有浏览器显示完全相同的演示是错误的。如前所述,我们在五大电子商务网站之一工作。并让我们组织中的每个人都同意 IE6 到 IE8 的降级状态。这意味着,如果你用 IE6 到 IE8 浏览我们的网站,你仍然会看到一个很棒的网站。它只是不会有圆角和阴影。
不再坚持每个浏览器都得到相同的待遇,这大大提高了开发速度、更精简的代码、响应性更强的设计选项和更快的页面加载速度(因为没有任何额外的 HTTP 请求)。就像我们的统计数据显示的那样,旧浏览器上的方形角和无阴影框有很大的优势,但我们的访问者很少使用。
当然,这取决于你和你的雇主或顾客来决定这是否是你的一个选择,你仍然可以按照你喜欢的方式制作所有这些菜单。尽管如此,我们还是强烈建议向旧浏览器宣传降级状态的概念。这对每个人来说都是一个胜利。由于使用旧浏览器的人不经常看到尖端的设计,他们不太可能注意到他们没有在你的网站上得到它,而你获得了更干净的代码库的所有好处。当然,这并不是给使用旧浏览器的访问者一个糟糕体验的许可;他们仍然应该获得浏览器所能提供的最佳体验。正如我们的技术评论员(嗨,杰夫)指出的,这很像在黑白电视上看彩色电视节目;肯定有什么东西不见了,但这是观众得到的。电视摄制组所能做的就是努力确保画面至少能在黑白电视上显示出来。最后,随着圆角在设计界失去声望,我们都在无缘无故地追逐回报递减。
图 7-6 展示了 IE8 参观者实际看到的。它仍然提供了正确的信息和功能,看起来也不错,特别是对于那些很少看到圆角的访问者。(杰伊说它看起来更干净,事实上,但他从来不喜欢圆角。)
图 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 背景图像。
注意微软过滤器会导致性能非常差。如果没有它们也可以,那就跳过它们。我们在这里包括它们是为了支持 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 渐变生成器。你可以在 http://www.colorzilla.com/gradient-editor/找到它。
本质上,它是一个非常强大的(“终极”非常接近)渐变生成器,输出一段相当防弹的代码,以确保您的渐变要么显示良好,要么逐步后退。一切为了低价,低价的免费!让我们来看看他们界面中的渐变,如图图 7-7 所示。
图 7-7。终极 CSS 渐变生成器
请注意,在“预置”面板中,您可以从许多不同的起点中进行选择。你也可以一头扎进去,忽略预设,从头开始酝酿你自己的渐变。
预设面板下方是神奇的地方。您可以通过输入名称并点击保存来保存您的渐变。如果你以后需要修改你的作品,这很方便。渐变的实际输入在保存功能下面。
图 7-8。渐变选择器
在图 7-8 的顶部,两个黑框构成了不透明度选择器。对于这个例子,我们将使用 100%的不透明度(这就是为什么手柄是纯黑色),但你可以选择用不透明度做一些真正有趣的组合。我们鼓励你去尝试。底部的一排方框是定义颜色的地方,也是“停止”的地方。以百分比定义的停止点基本上告诉工具颜色可能在哪里变化。然后,该工具负责计算出在停止点之间映射的颜色,以创建平滑的渐变。您可以通过双击方块来更改颜色,并通过单击左侧或右侧的相邻区域来进行更多停留。您也可以更改颜色,方法是点按其中一个颜色框,然后点按其下方“停靠点”面板中的颜色框。这有助于选择颜色的替代方法,因为双击时很容易不小心移动颜色框。
图 7-9 显示了我们选择的颜色。
图 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]>
`这是我们的老朋友,条件语句。如果浏览器是 IE9,该语句禁用过滤器。从那里开始,你只需要将gradient
类添加到适用的元素中,如前面的清单 7-3 所示。
除了一长串的浏览器前缀外,它还支持这些浏览器的不同版本。如果所有这些都失败了,它将定义一个与滑块中的第一种颜色相匹配的平面背景颜色。
接下来,ul
元素中有li
元素;它们构成了第 1 层和第 2 层导航元素的容器。图 7-10 提醒你它的样子。
图 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 显示了游戏子菜单。
图 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 显示了许多可以设置动画的属性。
作者注是的,这是一场漫长的战役,我们还没有描述完我们的菜单是如何运作的。请容忍我们。如果这算是一种安慰的话,这也是一个很长的章节(重写了几次)。
现在让我们来设计下拉列表中链接的样式。让我们再次使用一个包含锚标签的无序列表。清单 7-10 显示了ul
和li
类的 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 所示。
图 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; }
提示正如我们在第一章中提到的,实用主义战胜了纯粹主义。已经说过我们从不通过 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 显示了在mouseenter
和mouseleave
事件上设置类的 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 函数。(顺便说一下,这些函数是匿名的,在运行时由它们的签名来标识——也就是参数的数量。)它添加了mouseenter
和mouseleave
事件处理程序。延迟为零的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 的访问者提供了一个解决方案。
我们强烈建议你将这个想法应用到你自己的导航元素和你开发的网站的其他方面,只要这样的技术对你的访问者有好处。
八、报头
在最后一章的马拉松之后,我们认为你可能想要休息一下。因为我们没有太多关于报头的内容要说,所以我们决定用这个简短的章节来平衡这个长章节。我们对报头的基本建议是保持简洁。不要试图把所有的东西(或者任何超出基本的东西)都塞进报头。
“报头”一词来源于帆船时代。想象一下,一根桅杆上挂着一堆巨大的方形帆,顶部有一面旗帜(字面意思是在桅杆的顶端)。帆完成了所有的工作。这面旗帜赢得了所有的荣誉,因为它承载着对那艘船上的人和其他船上的人都非常重要的信息。通过显示国籍,它是船上的人的骄傲,也是其他人的朋友或敌人的指示器。
当报纸变得司空见惯时,他们采用了刊头的概念作为部分版面的隐喻。一份报纸的大部分版面都被自己的“帆”占据了:它的版面——传递信息的大块区域,包括新闻报道和广告。刊头标识纸张,就像旗帜标识船只一样。这个比喻是一个方便的提醒,即刊头的工作是识别或标记报纸。换句话说,在报纸和网站上,刊头是识别品牌的地方,也是展示其他方便信息的地方。
在网页设计中,标题是品牌标志的位置,是设置网站外观和感觉的起点。此外,报头通常包含重要的导航项目:“关于我们”、“联系我们”、“购物车”等。在下面的例子中,我们使用电子商务网站上常见的内容:徽标、帐户、购物车和电话。国际网站上常见的一个项目是国家/语言选择器。虽然我们没有在示例站点中包括它,但是我们提到了一种加速这个特性的性能技术。
构建更好的国家选择器
在许多国家销售的大型网站通常会提供一个下拉菜单,包含他们提供的每个国家/语言选项。图 8-1 显示了处于关闭状态的典型语言选择器。
图 8-1。一个大型电子商务网站关闭状态下的典型语言选择器
图 8-2 显示了同一语言选择器处于打开状态。
图 8-2。一个大型电子商务网站打开状态下的典型语言选择器
这种设计元素会变得非常复杂,有时会提供 50 种甚至更多的组合。虽然可以将选择框抽象为一段动态引用的代码,但每次都必须下载生成选择列表的 HTML。在图 8-1 和图 8-2 所示的国家选择器中,每个访问者的每个页面都插入了 200 多行代码。对于一个大多数访问者从不使用的功能来说,这是一个很大的开销,而且很少有访问者会使用一次以上。
一个更好的方法是链接到一个国家/语言页面,以一种更有意义的方式列出可用的选项,也许还带有易于识别的标志。因为您已经有了一整页要处理,所以这些标志可以比下拉国家选择器中的标志更大。这种技术节省了大量的 HTML 生成,并且需要呈现的元素更少。此外,考虑到访问者通常只设置一次他们的位置和语言偏好。因此,让他们在继续购物之前进入这个页面并不是一个很大的障碍。带宽的节省(在较小的程度上,页面加载时间)是相当可观的,大多数访问者永远不会注意到这种差异。
看起来&感觉起来
在我们的示例电子商务站点中,我们要有一个非常直观的外观:只有一个容纳报头的容器和一些绝对放置的元素来传达关键信息。图 8-3 显示了样本站点的报头。
图 8-3。刊头
清单 8-1 显示了定义报头的 HTML。
清单 8-1。报头 HTML
`
![](img/clikz-sprite.png)
报头 HTML 没有令人兴奋的内容;我们可能都多次见过类似的 HTML。然而,我们确实想指出一件事:带有类headNav twelvecol
的头部包装器。twelvecol
标识符指的是我们正在使用的灵活网格。我们稍后会谈到这一点。
清单 8-2 显示了样式化报头的 CSS。
清单 8-2。报头 CSS
.headNav { height: 70px; } .logo_166 { position: absolute; top: 12px; clip: rect(2px, 168px, 48px, 2px); } .headNav { position: relative; font-size: 12px; } .acctNav { position: absolute; right: 0; top: 15px; } .acctNavLI { float: left; margin-left: 20px; } .acctNavA, .acctNavA:visited { text-decoration: none; color: #666666; display: inline-block; } .acctNavA:hover { text-decoration: underline; } .mhCart { padding-right: 23px; } .mhCart:hover { text-decoration: none; } .mhCart:hover .cartText { text-decoration: underline; } .icon_cart { background: url(../img/clikz-sprite.png) no-repeat -22px -54px; width: 19px; height: 16px; position: absolute; z-index: 1; right: 0; top: 0; } .mhContactPhone { float: right; clear: both; margin-top: 5px; }
我们应该在这里指出一点诡计:品牌标志的图像元素。我们选择使用图像而不是背景图像的原因只有一个:打印。你不能指望浏览器打印背景图片。事实上,你可以指望浏览器而不是打印背景图片。也有例外,比如用户在浏览器中改变了偏好,但我们当然不能依赖于此。所以我们选择了图像元素。然而,我们不能忍受额外的 HTTP 请求对性能的影响,所以我们将徽标添加到用于其他元素的 sprite 中,并剪切掉 sprite 图像的其余部分,只留下徽标。
CSS 剪辑
我们在清单 8-2 中提到的技术是新旧技术的结合。clip 属性支持 IE6,但是图像精灵的广泛采用是新的(嗯,不完全是新的,但是更新了)。
那么我们来谈谈它是如何工作的。首先,考虑清单 8-3 。
清单 8-3。定义一个裁剪矩形
clip: rect(2px, 168px, 48px, 2px);
clip
属性可以采用的唯一形状是rect()
。(记住浏览器只画矩形。)里面有 CSS 经典顺序。(记住顺序的一个简单方法就是想想回忆顺序是多少:上、右、下、左。)我们使用这种顺序是因为这与大多数浏览器在呈现页面区域时使用的顺序相同。我们正在描述一个盒子,从图像的左上角开始定义。这个想法可能有点难以理解,所以让我们看一个图表。图 8-4 显示了我们的精灵。
图 8-4。我们的雪碧
在本例中,我们只需要左上角的徽标部分。图 8-5 显示了具有各种特征的精灵的更详细视图。
图 8-5。雪碧特性
棘手的部分可能是可视化的底部和右侧。一个简单的方法是把它们看作一个总和:它们的边距加上我们想要的图像的大小。清单 8-4 显示了伪代码中的细节。
清单 8-4。雪碧伪码
Bottom = Top (2px) + height of image (46px); Right = Left (2px) + width of image (166px);
为了实现这一点,我们还必须将position
属性设置为absolute
。现在我们有了一个像精灵一样工作的图像元素。很酷,不是吗?好吧,不管酷不酷,这是一个很方便的技巧。
图标链接
在购物车链接中,我们有一个购物车图标。这种效果可以通过许多不同的方式来实现,但让我们坚持一种一直都很可靠的方式。清单 8-5 显示了 HTML。
清单 8-5。报头链接的 HTML】
<a href="javascript:;" class="acctNavA mhCart"> <span class="cartText">Cart</span> <i class="icon_cart ir"></i> </a>
清单 8-6 显示了 CSS。
清单 8-6。 CSS 样式的报头链接
.mhCart { padding-right: 23px; } .mhCart:hover { text-decoration: none; } .mhCart:hover .cartText { text-decoration: underline; } .icon_cart { background: url(../img/clikz-sprite.png) no-repeat -22px -54px; width: 19px; height: 16px; position: absolute; z-index: 1; right: 0; top: 0; }
如您所见,文本和图像都在锚标记内。这使得它们都可以点击。
注意为了定义图像,我们使用了一个< i >元素。对于关心语义标记的人来说,这似乎很奇怪,因为这是将文本显示为斜体的老方法。在网络的早期,当< i >元素很普遍时,这种时尚转移到了< em >(强调)元素,以满足斜体文本的需要。如今< i >和< b >都卷土重来,用于视觉造型,代替文字表示。因此,虽然这种对< i >元素的使用可能有点偏离预期用途,可能会让一些读者觉得奇怪甚至是错误的,但使用< i >元素来表示图标实际上是相当时尚的。简而言之,将< i >和< b >元素视为纯粹的样式而非语义标记。
您可以在清单 8-6 的规则集中看到购物车链接的 CSS。我们将使用之前解释过的填充技巧,给锚点足够的填充来容纳图标,中间留一点空间来填充文本。现在我们在做饭,但还有一个问题。因为我们已经将锚的状态定义为text-decoration: underline
,所以图标也加了下划线。那不行;我们只希望文本有下划线。下面是解决这个问题的方法:用一个类spanText
在文本周围添加一个 span。在锚点中,禁用从默认锚点规则集中继承的:hover
状态。然后使用一个具有:hover
状态的后代选择器,如清单 8-7 所示。
清单 8-7。后代选择器:悬停状态
.mhCart:hover { text-decoration: none; } .mhCart:hover .cartText { text-decoration: underline; }
这样,当访问者将鼠标悬停在带有.mhCart
标识符的元素上时,span.cartText
元素会得到一条下划线,但图像不会。
我们认为额外的标记是值得的,因为它让我们可以选择文本和图标。此外,它包含了“在盒子里思考”的范例,让我们以模块化的方式保持我们的关注。
总结
在这短短的一章中,我们讲述了
- “报头”的起源及其在印刷和网络中的使用
- 呈现国家和语言选择器的更好方式
- 如何使用 sprite 为图像元素提供内容
- 如何在一个容器中组织文本和图像,并让它们正确地突出显示
所有这些加起来就是如何制作一个网页的报头。
我们想强调一个我们在访问各种网站和与商务人士打交道时多次遇到的警告。不要试图做太多的报头。一旦你超越了一个标志,一个购物车,和基本的联系信息,刊头就开始变得太忙了。在这一点上,你开始削弱你的品牌,让你的客户更难找到必要的信息。
最后但并非最不重要的一点是,过于繁忙的报头确实会影响性能,尤其是当您试图为每个客户定制报头时。如果你必须定制报头(我们已经做过了),尽可能地进行广泛的测试和优化,以减少页面加载时间。另外,不要把测试留到开发过程的最后。早期,这是个问题;在最后一刻,这是一个危机,没有人需要那种心痛。套用一位著名的芝加哥政治家的话:尽早测试,经常测试。总的来说,这是一个好建议,尤其是当你在报头中添加自定义内容的时候。记住,每一页都有刊头,所以一个糟糕的刊头就是一场噩梦。
九、页脚
许多网站都有页脚。通常,它是放置法律信息(如版权和商标声明)和适用于每个页面的一些内容的地方。每个在网上冲浪超过 10 分钟的人都知道,如果你在页眉中找不到联系人和关于链接,就在页脚中找。
页脚的另一个用途,也是我们将要使用的,是作为存放站点地图的地方。因为页脚出现在每个页面上,所以站点地图也总是出现。这使得那些从站点地图角度考虑的访问者的导航更加容易,对于那些喜欢搜索或浏览以找到他们喜欢的内容的访问者来说,这仍然是一个不错的选择。
因为页脚很简单,否则这一章会很短,所以我们也将利用这一章来介绍在网站上使用 SVG 的想法。SVG 可以很好地促进页面加载,这一点我们将在本章后面发现。
让我们先来看看我们对页脚有什么想法。然后我们将讨论如何实现它。图 9-1 显示了我们的示例站点[
clikz.us](http://clikz.us)
的页脚
图 9-1。我们的样本网站的页脚
制作页脚
制作页脚的方法有很多:手工编写每个链接的代码,通过许多不同的服务器端机制生成链接,甚至使用 JavaScript 模板。(不过,我们建议不要使用 JavaScript 模板;它不能产生最佳性能,并且对搜索机器人不友好)。在这种情况下,我们将把页脚内容存储在服务器上的一个单独的文件中。该文件还包含网站菜单的内容,这是页脚的第一次性能提升。
为了创建站点地图中的所有链接,我们不打算创建新内容。相反,我们将使用 CSS 来重新设计菜单中已有的内容。除了不会两次下载相同的信息所带来的性能提升之外,我们还可以很好地提升开发人员的性能。当您向菜单中添加项目时,它也会自动出现在站点地图中。这省去了添加两次的麻烦,也消除了出现错误的可能性,因为你记住了一个地方,却忘记了另一个地方。我们强烈反对两次输入相同的内容,不仅仅是因为我们懒惰,还因为手动复制经常是错误的来源。
为了便于说明,让我们假设一个 PHP 支持的站点。因此,导航信息(无论是菜单还是站点地图)位于一个文件中,该文件包含一些对 PHP 呈现引擎有意义的特殊标记。我们可以很容易地选择许多其他技术,包括.Net
(通过局部视图)、JavaServer Pages、JavaServer Faces 等等。
因此,首先我们必须将导航 HTML 从主模板中移除,并将其放入另一个文件中。在本例中,我们将代码放在一个includes
文件夹中,并将文件命名为siteNavigation.php
。为了使菜单和站点地图的样式不同,我们需要一种方法来设置一个唯一的选择器来匹配我们的 CSS。我们通过在 PHP include 上面设置一个变量来创建惟一的选择器,并在 HTML 中使用这个变量。清单 9-1 显示了siteNavigation.php
的内容。第一行(加粗)显示了我们设置选择器的位置,该选择器应用样式来创建菜单或站点地图。我们还加粗了设置一些辅助文本的行,帮助屏幕阅读器为视力有问题的访问者提供更少痛苦的体验。
清单 9-1。【siteNavigation.php 内容,变量高亮
`
`这两个变量是我们所需要的,$navContext
变量标识使用哪组样式(菜单或站点地图)。$navContext
变量的值可以是nmDropDown
或footerNav
。给定这些值,我们可以为相同的信息创建两种非常不同的布局。我们还将$assistiveNav
变量的值设置为对屏幕阅读器有意义的文本,这样使用屏幕阅读器的访问者就知道他们遇到了一个菜单或站点地图,并有机会跳过它。每个网页设计者都应该在屏幕阅读器中收听他们的网站。每次访问另一个页面,你都会听到上百个导航链接,你很快就会准备好面对屏幕。
设置变量是一项简单的任务。为了给菜单设置正确的值,把代码放在标题下面的清单 9-2 中。
清单 9-2。为菜单设置变量值
`
`要为菜单设置正确的值,将清单 9-3 中的代码放在页脚的顶部。
清单 9-3。为页脚设置变量值
`
`因此,在每个实例中,我们都定义了一段由服务器处理的代码。这就是<?php //serverside code goes here ?>
形式的指令所做的。在这种情况下,我们为节(菜单或页脚)设置适当的变量,然后直接在变量声明下面输出siteNavigation.php
的内容(用适当的特定于节的值填充)。我们得到的是不同的类名,这让我们可以为两个用例中的每一个恰当地设计 HTML 样式。例如,如果我们想在菜单和页脚中设置不同的第一层锚标签样式,我们可以使用清单 9-4 中所示的 CSS。
清单 9-4。对菜单和页脚中的 1 级锚点标签进行不同的样式化
`.nmDropDown .nmA {
/* only styles the main navigation; /
}
.footerNav .nmA{
/ only styles the footer navigation; */
}`
正如我们在本章前面提到的,这种机制让我们只维护.siteNavigation.php
(而不是同一个文件中的两个源或两个内容块)并在主站点导航和页脚之间共享那个文件中的内容。
除了本章前面提到的性能优势之外,突出站点地图还为站点访问者和运营站点的企业提供了另一个实质性的优势。用户可以一目了然地看到网站的分类。但是如果有人说,“嗯,这不就是网站地图的作用吗?”答案是好处可能不太明显。假设访问者可能对某个链接非常感兴趣,但是这个链接属于用户认为很无聊的类别。有了高度可见的站点地图,访问者更有可能点击那个链接。没有网站地图,访问者将永远找不到它,企业也将错过销售该产品的机会。这对各方来说都是一个失败的提议。访问者找不到感兴趣的产品,企业失去了销售,web 开发人员错过了让访问者找到正确内容的机会。所以,是的,这是显而易见的,但好处可能是显著的。将所有链接都放在页脚的最后一个优点是,它为基于 JavaScript 的主导航提供了备份(例如我们用来显示导航的下拉菜单)。如果你的网站使用基于 JavaScript 的菜单,但是访问者关闭了 JavaScript,他们什么也找不到,会很快转到其他网站。结果呢?你刚刚失去了一个机会。此外,搜索机器人通常不会遍历基于 JavaScript 的菜单。对于这两种情况,拥有一个网站地图,提供一种替代方式来连接访问者和搜索机器人的内容,让你在游戏中。
设计页脚的样式
理论到此为止。让我们来看看设计页脚和它包含的站点地图背后的代码。清单 9-5 显示了样式页脚所需的所有 CSS 代码。这是一个巨大的清单,所以请耐心等待。我们将在本章的后面解释每一部分的作用。
清单 9-5。样式页脚的 CSS
`.siteGrad {
background: #e6272b; /* Old browsers /
/ IE9 SVG, needs conditional override of 'filter' to 'none' /
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw
Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl
c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk
aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag
b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2U2MjcyYiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjE1
JSIgc3RvcC1jb2xvcj0iI2YxMjgyZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjI3JSIgc3RvcC1j
b2xvcj0iI2YyMjkyZSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUwJSIgc3RvcC1jb2xvcj0iI2Uz
MjYyYiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9Ijc3JSIgc3RvcC1jb2xvcj0iI2NhMjIyNiIgc3Rv
cC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9Ijg1JSIgc3RvcC1jb2xvcj0iI2M2MjEyNSIgc3RvcC1vcGFjaXR5
PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNjNjIxMjUiIHN0b3Atb3BhY2l0eT0iMSIvPgog
IDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNn
cmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
background: -moz-linear-gradient(top, #e6272b 0%, #f1282d 15%, #f2292e 27%, #e3262b 50%, #ca22
26 77%, #c62125 85%, #c62125 100%);/ FF3.6+ /
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e6272b), color-
stop(15%, #f1282d), color-stop(27%, #f2292e), color-stop(50%, #e3262b), color-stop(77%, #ca2226)
, color-stop(85%, #c62125), color-stop(100%, #c62125));/ Chrome,Safari4+ /
background: -webkit-linear-gradient(top, #e6272b 0%, #f1282d 15%, #f2292e 27%, #e3262b 50%, #c
a2226 77%, #c62125 85%, #c62125 100%);/ Chrome10+,Safari5.1+ /
background: -o-linear-gradient(top, #e6272b 0%, #f1282d 15%, #f2292e 27%, #e3262b 50%, #ca2226
77%, #c62125 85%, #c62125 100%);/ Opera 11.10+ /
background: -ms-linear-gradient(top, #e6272b 0%, #f1282d 15%, #f2292e 27%, #e3262b 50%, #ca222
6 77%, #c62125 85%, #c62125 100%);/ IE10+ /
background: linear-gradient(top, #e6272b 0%, #f1282d 15%, #f2292e 27%, #e3262b 50%, #ca2226 77
%, #c62125 85%, #c62125 100%);/ W3C /
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#e6272b', endColorstr='#c6212
5', GradientType=0);/ IE6-8 */
}
.mainFooter {
clear: both;
padding: 18px 0;
text-align: center;
}
.mainFooter .row {
overflow: visible;
}
nav.mainFooterWrap {
box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.2), inset 0px 0px 3px #888888;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-moz-background-clip: padding;
-webkit-background-clip: padding-box;
background-clip: padding-box;
background: #fafafa;/* Old browsers */
/* IE9 SVG, needs conditional override of 'filter' to 'none' /
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw
Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl
c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk
aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag
b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZhZmFmYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjE1
JSIgc3RvcC1jb2xvcj0iI2ZhZmFmYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjI3JSIgc3RvcC1j
b2xvcj0iI2ZhZmFmYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUwJSIgc3RvcC1jb2xvcj0iI2Zm
ZmZmZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9Ijc3JSIgc3RvcC1jb2xvcj0iI2Y5ZjlmOSIgc3Rv
cC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9Ijg1JSIgc3RvcC1jb2xvcj0iI2Y5ZjlmOSIgc3RvcC1vcGFjaXR5
PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNmOWY5ZjkiIHN0b3Atb3BhY2l0eT0iMSIvPgog
IDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNn
cmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+);
background: -moz-linear-gradient(top, #fafafa 0%, #fafafa 15%, #fafafa 27%, #ffffff 50%, #f9f9
f9 77%, #f9f9f9 85%, #f9f9f9 100%);/ FF3.6+ /
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fafafa), color-
stop(15%, #fafafa), color-stop(27%, #fafafa), color-stop(50%, #ffffff), color-stop(77%, #f9f9f9)
, color-stop(85%, #f9f9f9), color-stop(100%, #f9f9f9));/ Chrome,Safari4+ /
background: -webkit-linear-gradient(top, #fafafa 0%, #fafafa 15%, #fafafa 27%, #ffffff 50%, #f
9f9f9 77%, #f9f9f9 85%, #f9f9f9 100%);/ Chrome10+,Safari5.1+ /
background: -o-linear-gradient(top, #fafafa 0%, #fafafa 15%, #fafafa 27%, #ffffff 50%, #f9f9f9
77%, #f9f9f9 85%, #f9f9f9 100%);/ Opera 11.10+ /
background: -ms-linear-gradient(top, #fafafa 0%, #fafafa 15%, #fafafa 27%, #ffffff 50%, #f9f9f
9 77%, #f9f9f9 85%, #f9f9f9 100%);/ IE10+ /
background: linear-gradient(top, #fafafa 0%, #fafafa 15%, #fafafa 27%, #ffffff 50%, #f9f9f9 77
%, #f9f9f9 85%, #f9f9f9 100%);/ W3C /
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fafafa', endColorstr='#f9f9f
9', GradientType=0);/ IE6-8 */
}
.footerNav {
display: inline-block;
padding-top: 20px;
}
.footerNav .nmLI {
float: left;
margin-right: 30px;
}
.footerNav .nmA {
color: #777;
box-shadow: 0 0 3px #999;
font-size: 14px;
display: block;
padding: 2px 20px;
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
-moz-background-clip: padding;
-webkit-background-clip: padding-box;
background-clip: padding-box;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5), inset 0 0 2px rgba(0, 0, 0, 0.5);
background: #ffffff;/* Old browsers /
/ IE9 SVG, needs conditional override of 'filter' to 'none' /
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw
Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl
c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk
aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMTAwJSIgeDI9IjEwMCUiIHkyPSIwJSI+CiAgICA8c3Rv
cCBvZmZzZXQ9IjAlIiBzdG9wLWNvbG9yPSIjZmZmZmZmIiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0i
MTUlIiBzdG9wLWNvbG9yPSIjZjlmOWY5IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iNTAlIiBzdG9w
LWNvbG9yPSIjZmZmZmZmIiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iNzElIiBzdG9wLWNvbG9yPSIj
ZjhmOGY4IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iODUlIiBzdG9wLWNvbG9yPSIjZmZmZmZmIiBz
dG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2Y4ZjhmOCIgc3RvcC1vcGFj
aXR5PSIxIi8+CiAgPC9saW5lYXJHcmFkaWVudD4KICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBm
aWxsPSJ1cmwoI2dyYWQtdWNnZy1nZW5lcmF0ZWQpIiAvPgo8L3N2Zz4=);
background: -moz-linear-gradient(45deg, #ffffff 0%, #f9f9f9 15%, #ffffff 50%, #f8f8f8 71%, #ff
ffff 85%, #f8f8f8 100%);/ FF3.6+ /
background: -webkit-gradient(linear, left bottom, right top, color-stop(0%, #ffffff), color-
stop(15%, #f9f9f9), color-stop(50%, #ffffff), color-stop(71%, #f8f8f8), color-stop(85%, #ffffff)
, color-stop(100%, #f8f8f8));/ Chrome,Safari4+ */
background: -webkit-linear-gradient(45deg, #ffffff 0%, #f9f9f9 15%, #ffffff 50%, #f8f8f8 71%,
ffffff 85%, #f8f8f8 100%);/* Chrome10+,Safari5.1+ */
background: -o-linear-gradient(45deg, #ffffff 0%, #f9f9f9 15%, #ffffff 50%, #f8f8f8 71%, #ffff
ff 85%, #f8f8f8 100%);/* Opera 11.10+ /
background: -ms-linear-gradient(45deg, #ffffff 0%, #f9f9f9 15%, #ffffff 50%, #f8f8f8 71%, #fff
fff 85%, #f8f8f8 100%);/ IE10+ /
background: linear-gradient(45deg, #ffffff 0%, #f9f9f9 15%, #ffffff 50%, #f8f8f8 71%, #ffffff
85%, #f8f8f8 100%);/ W3C /
filter: progid :dximagetransform.microsoft.gradient(startColorstr='#ffffff',
endColorstr='#f8f8f8', GradientType=1);/ IE6-8 fallback on horizontal gradient */
}
.footerNav .ie9 .nmA {
filter: none;
}
.footerNav .nmSlideout {
height: auto;
padding: 10px;
}
.footerNav .nmUL-L2 {
padding-bottom: 20px;
}
.footerNav .nmA-L2 {
color: #888;
font-size: 12px;
text-align: right;
display: block;
padding-bottom: 2px;
}
.footerNav .nmA-L2:first-word {
color: #00F;
font-weight: bold;
}
.footerNav .searchWrap {
display: none;
}
.mainFooterUL {
display: inline-block;
}
.mfLI {
float: left;
padding: 0 10px;
border-right: 1px solid #999;
}
.mfLI.mfLast {
border: 0;
}
.mfA {
font-size: 12px;
color: #999;
}
.mainFooterWrap {
text-align: center;
padding-bottom: 20px;
}
.legalWrap {
background: #FFF;
display: inline-block;
padding: 10px 20px;
-webkit-border-radius: 10px;
-moz-border-radius: 10px;
border-radius: 10px;
-moz-background-clip: padding;
-webkit-background-clip: padding-box;
background-clip: padding-box;
box-shadow: inset 1px 1px 2px rgba(0, 0, 0, 0.5);
}
.legalText {
margin: 0;
font-size: 12px;
color: #777;
}
.legalText a {
color: #555;
text-decoration: underline;
}`
大部分代码非常简单,与我们做过的其他事情相似。然而,我们想指出一些关于页脚布局代码的事情。我们将从我们的.footerNav .nmA
规则集中的双框阴影开始,如下行所示:
box-shadow: 0 0 3px rgba(0, 0, 0, 0.5), inset 0 0 2px rgba(0, 0, 0, 0.5);
那一行有三个项目需要我们注意。首先,我们使用 RGBA,因为它允许我们通过 Alpha (A)值设置透明度值。在本例中,我们将透明度设置为 50(0.5
)。结果是透明度为 50%的黑色阴影,这使它们变成深灰色,并让背景中的项目透过阴影显示出来。
第二,我们使用了一个插入值,由于光源似乎来自左上角,这使得这个框看起来像是缩进了页面中。(光源可能来自任何地方,但左上角是标准。)在版权声明周围可以看到嵌入效果。
关键字 inset ,告诉浏览器我们想要盒子里面有阴影。这可以用来使盒子的内部看起来是嵌入式的,意思是凹陷的,阴影被投射在高度差中,通常灯光模拟来自左上角。我们在版权声明周围设置了这种效果。该效果由.legalWrap
类中的以下行定义:
box-shadow: 1px 1px 2px rgba(0,0,0,.5);
提示方框阴影的一般格式如下:
box-shadow : horizontal_offset vertical_offset blur_radius color;
然而,我们不希望导航的副标题出现凹陷。事实上,我们想要相反的效果:一点点提升。为了实现这种外观,我们设置阴影来帮助定义边缘,并给链接一点高度。所以我们在.footerNav .nmA
类中设置了如下阴影:
Box-shadow: 0 0 2px rbga(0,0,0,.5);
这使得在站点地图中包含类别的框的整个内部周围有 2 个像素的相等阴影。它创造了一种斜面效果。
要注意的第三点是,我们有多个阴影定义,用逗号分隔。您可以添加任意数量的阴影,用逗号分隔各个阴影。要了解有多疯狂,请访问另一个保罗爱尔兰网站,看看他如何在文本上使用阴影: http://mothereffingtextshadow.com/
我们建议谨慎使用阴影,无论你在哪里使用它们,除非你正在创建一个展示阴影的站点。野生阴影会使访问者阅读和导航更加困难。尽管如此,如果使用得当,它们可以将访问者的注意力吸引到重要的地方,所以它们是一个好主意。
SVG
正如我们在本章开始时提到的,页脚可能有点无聊。为了让它更有活力,我们通过 SVG(可缩放矢量图形)增加了一点渐进的增强。SVG 对于某些事情非常有用。然而,Internet Explorer 直到版本 9 才支持 SVG。因此,我们通过 Modernzr 使用我们通常的特征检测技巧,并且只在浏览器知道如何处理 SVG 时才显示它。SVG 确实在运行 WebKit 浏览器的移动设备上大放异彩(iPhone、Android、Palm)。然而,我们也需要支持旧的浏览器,所以我们将再次使用渐进式改进来确保所有访问者得到的页面看起来和他们的浏览器所能提供的一样好。
在我们开始实现之前,我们需要解释一些关于 SVG 的事情。SVG 存储为 XML 的一种方言,对应于万维网联盟定义的模式。当前的推荐标准(版本 1.1)可以在 W3 网站上找到:[
www.w3.org/TR/SVG11/](http://www.w3.org/TR/SVG11/)
SVG 允许创建复杂的形状,然后可以正确地缩放到任何大小。几年前,Jay 使用 SVG 为一个客户创建了一个四英尺高的金属标牌。因为是 SVG,他可以用彩色激光在普通信纸大小的纸上打印出标志的草稿。一旦客户对设计感到满意,他就把文件交给一家金属制造公司,该公司用这个图像制作了这个标志。因为 SVG 可以缩放到任何大小,所以 Jay 不必担心在呈现四英尺高时标牌看起来很好。客户对此很满意,这个标志仍然挂在德克萨斯州奥斯汀的一栋建筑的侧面。
SVG 能够无限伸缩的秘密在于它的渲染方式。SVG(或任何其他矢量图像格式)不像光栅图像格式那样定义一堆像素。相反,它描述了各种形状以及它们之间的关系。如果您曾经上过几何课,您就会确切地知道 SVG 是如何工作的。它使用简单形状(圆形、三角形、正方形、矩形和其他多边形)之间和内部的相同数学关系来渲染任意大小的图像。复杂的形状,如字体中的字母,被渲染为一组简单的形状,但这是渲染引擎处理的细节(这也是一件好事,否则使用 SVG 会很痛苦)。无论您是需要网页上的徽标、四英尺高的金属标志、广告牌,还是飞艇侧面的消息,SVG 都可以缩放,以便在该尺寸下正确打印图像(没有像素化或其他不幸的打印瑕疵)。
设计人员可以手工创建 XML 来定义 SVG 图像,这对于简单的图像来说已经足够好了。然而,使用一个好的绘图工具,然后将结果保存为 SVG 通常更容易。幸运的是,Adobe Illustrator、CorelDraw、Inkscape 和其他各种软件都可以保存为 SVG。此外,Google Code 项目包括一个 SVG 绘图工具。它在[
svg-edit.googlecode.com/svn/branches/2.5.1/editor/svg-editor.html](http://svg-edit.googlecode.com/svn/branches/2.5.1/editor/svg-editor.html)
。最后,Apache 通过其 XML Graphics 项目维护了一个简单的工具,可以方便地进行 SVG 实验。叫蜡染,在[
xmlgraphics.apache.org/batik/](http://xmlgraphics.apache.org/batik/)
。杰用蜡染做了那个金属标牌。
那么我们页脚中的 SVG 在哪里呢?最上面的部分给页脚部分提供了深度的错觉。这个想法是创造一个顾客的产品放在上面的柜台的假象。不过,主要是为了向您展示一些简单的 SVG。
我们在页脚使用的 SVG 定义了简单的形状(一个浅灰色的条,两端向内倾斜),如图 9-2 所示。
图 9-2。页脚中的 SVG 图像。
清单 9-6 显示了生成 SVG 图像的代码和包含它的div
元素。
清单 9-6。footerTopHolder div 元素
`
`在我们深入研究使用 SVG 元素的细节之前,让我们考虑一下这样做的好处。在这种情况下,SVG 占用 182 个字节。同样大小(1133 × 19)的 PNG 图像需要 290 字节。更好的是,我们保存了一个 HTTP 请求,因为 SVG 内容在 HTML 中。这两个因素都给了我们比图像更好的表现。
现在让我们花点时间来看看svg
元素。元素的开始标签包含了许多我们出于各种原因需要的属性。我们需要id
属性,这样我们就可以使用 JavaScript 来修改图像。我们将在这一章的后面讨论这个问题。我们需要version
和xmlns
属性来为浏览器识别我们正在创建什么样的图像。x
和y
属性定义了形状的原点。width
属性被设置为 100 %,它指定图像应该占据所有可用的宽度。height
属性指定高度应该是 19 像素。
提示视图框属性定义了渲染引擎的最小和最大高度和宽度。通常,它应该与 svg 元素的子元素的高度和宽度相匹配。在我们的例子中,我们只有一个子元素,所以我们将 viewBox 属性的大小设置为子元素的大小,这必须通过数学方法来推导。(当我们到达子元素时,我们将展示如何做到这一点。)
此外,设置视图框定义了高度与宽度的比率。由于不同的访问者使用不同宽度的浏览器(或者如果人们改变浏览器的宽度),这个比例保持不变。因此,条的高度随着宽度的变小而变小。在这种情况下,我们不希望这样;我们希望条的高度保持为 19 像素,不管条有多宽。为此,我们必须使用值为none
的preserveAspectRatio
属性,这意味着我们没有保留纵横比。将preserveAspectRatio
设置为none
会使图像填满所有可用空间,这给了我们想要的效果。如果这一切看起来不清楚,请原谅我们。当我们完成产品并展示最终的图像时,各种元素的相互作用会更加清晰。
path 元素包含两个属性。两者中最明显的一个是fill
属性,它指定填充颜色。由于我们使用的是实心和不透明填充,我们不需要其他与填充相关的属性,比如fill-rule
或fill-opacity
。d
(“数据”的缩写)属性包含更复杂的内容。它指定了创建我们想要的形状的路径。它通过下面的字符串定义了四个点:"M0,19L19,0h1094l19,19H0z"
。理解字符串如何定义路径的诀窍在于知道路径中的字母是什么意思。表 9-1 描述了各种路径定义指令(每个都是一个字符)及其含义。
注意我们已经讲述了基本的命令。还存在用于定义更复杂形状的其他命令。有关更多细节,请参见 W3 SVG 规范。您可以在
http://www.w3.org/TR/SVG/Overview.html
找到 SVG 规范
如果您仔细研究路径字符串,您可以看到它设置了一个初始位置(M0, 19
),绘制了四条线,最后关闭了路径(使用z
指令)。表 9-2 更详细地描述了这些指令。
通过计算绝对光标位置,正如我们在表 9-2 中所做的,你可以计算出svg
元素的viewBox
属性有多大。当然,我们实际上从期望的宽度 1133 开始向后工作,这是 1140 网格的宽度,带有一个小的边距。
为了确保包含的div.footerTopHolder
在 SVG 可用时出现,我们添加了清单 9-7 中的所示的代码。同样,我们依靠 Modernzr 来测试 SVG 支持,然后根据 modern Zr 的发现,我们将svg
或no-svg
添加到 class 属性中。
清单 9-7。移除浏览器无法显示的 SVG 图像
.no-svg .footerTopHolder { display:none; }
正如我们在本节前面提到的,SVG 占用 182 个字节。同样大小(1133 × 19)的 PNG 图像需要 290 字节。更重要的是,我们保存了一个 HTTP 请求,因为 SVG 内容在 HTML 中。这两个因素都产生了比图像更好的性能。
除了尺寸优势,使用 SVG 还有另一个好处:即使宽度改变,高度也可以保持不变。这不是您经常想做的事情,但它确实为 SVG 的另一个优点提供了一个有趣的演示。因为我们设置了width ="100%"
和height ="19px"
,所以当宽度改变时,浏览器确保高度保持 19 像素。
传统上,如果我们在这里使用一个图像,我们的浏览器窗口变小了,我们漂亮的响应网站适应了更小的尺寸,你会期望得到类似于图 9-3 和图 9-4 与图 9-3 和图 9-4 之间的差别。
图 9-3。原始尺寸
图 9-4。使用 SVG 缩小尺寸(50%),同时指定特定的高度和百分比宽度,产生高度不变的图像,如图图 9-5 所示。此外,因为浏览器中的 SVG 渲染引擎正在渲染一个数学构造(它是几何图形,还记得吗?),它永远不会产生任何令人遗憾的渲染伪像,比如像素化。
图 9-5。尺寸较小,高度固定
同样,这不是一个经常使用的技术,但是当你需要它的时候,它肯定是方便的。JavaScript 交互
现在让我们考虑使用 SVG 的最后一个好处。您可以通过 JavaScript 与图像进行交互。使用 jQuery 使得这种交互变得非常容易。只需使用.attr
方法并重置 SVG 中的各种值。清单 9-8 展示了一个可以对 SVG 图像做的事情的简单例子。
清单 9-8。用 JavaScript 修改 SVG 图像
$("#footerTop").attr("width", "200px"); $("#footerTop").find("path").attr("fill", "#0000FF");
而现在是 200 像素宽,蓝色,如图图 9-6 所示。
图 9-6。用 JavaScript 修改后的页脚栏
把你的注意力集中在线的末端。看到角度怎么不再是 45 度了?这是因为我们改变了svg
元素的宽度属性,但是没有改变 path 元素中d
属性的值。这种效果是在显示器顶部附近的某个地方出现了一个消失点,这就是我们想要的效果。
我们只是触及了 SVG 的皮毛。更有趣的是(对于本书来说,这是一个太大的主题)将 SVG 与canvas
元素结合使用。SVG 和canvas
元素为复杂的 Flash 功能、体验和交互提供了真正的替代方案。我们并不讨厌 Flash (Michael 曾长期从事 Flash 开发),但如果内容必须移动化,我们今天就不推荐使用 Flash。
不只是简单的形状
我们想分享关于 SVG 的最后一点想法。在我们示例网站的例子中,我们使用了一个非常简单的形状。然而,SVG 能够呈现复杂得多的图像。通过使用 Adobe Illustrator 之类的程序,您可以制作一些令人惊叹的 SVG 艺术作品。问题通常是如何平衡复杂性和大小。图形越复杂,正确渲染所需的代码就越多。有时,如果不考虑缩放或打印,使用图像会更有意义。
尽管如此,让我们看看 Illustrator 中内置的一些有趣的功能。它可以将复杂的图像(甚至是照片)转换成 SVG。然而,这些文件的大小通常非常大。为了好玩,Michael 在 Illustrator 中使用 Live Trace 制作了一个自己的 SVG 图像。你可以玩互动演示,让你在 http://clikz.us/svg.html
改变图像的大小。现在看看你能不能放大迈克尔的头来匹配他的自负。这个图像需要 600 千字节(600 kb)的文件大小,证明 SVG 对于复杂的图像(尤其是照片)并不总是一个好的选择。
图 9-7。迈克尔的自画像
总结
在本章中,我们描述了我们在示例站点中使用的页脚:[
clikz.us](http://clikz.us)
。
在这个过程中,我们演示了以下内容:如何重用内容来制作菜单和站点地图;如何用阴影突出显示页脚的重要部分;如何在网站上使用 SVG。
在描述如何使用 SVG 作为网页内容的过程中,我们演示了
- 对于简单的图像,SVG 使用更少的字节并减少 HTTP 请求的数量,这两者都有助于更好的页面加载时间和更少的网络流量。
- SVG 可以由 JavaScript 操纵,让您更改图像以响应页面上的事件;这种技术可以产生一些很棒的交互式页面。
- SVG 并不是渲染复杂图像的最佳方式,尤其是照片,尽管您可以从中获得一些乐趣。
我们不会在本书中进一步讨论页脚。然而,您可以在我们的示例站点的每个页面上看到它的运行:[
clikz.us](http://clikz.us)
。
本章总结了我们对本网站每个页面所用项目的描述。从这里开始,我们将投入到创建和使用可重用的 web 控件中,如第五章所述。
十、分形设计模式
“分形”可能看起来有点奇怪(特别是因为 Jay 在另一本书里写过严格数学意义上的分形)。在这种情况下,虽然,我们使用这个术语有点隐喻。根据严格的数学定义,分形是一种具有比例对称性的几何形状,这意味着你可以放大到任意深度,并看到相同的图案重复出现。图 10-1 展示了 Jay 通过编写 Java 类创建的分形树。它代表了我们从几个可重用组件构建许多页面的理念。
图 10-1。一棵分形树,象征递归容器设计模式
虽然我们没有展示数学上衍生的分形,但我们将描述一种设计模式,它包括重复相同的模式到我们需要的任何深度(有时是四层或更多层)。换句话说,我们将描述一个递归容器模式。
我们在第五章中提到了我们如何构建大型网站的理论基础。在这一章中,我们将更详细地探讨这个想法。在本书这一部分的剩余章节中,我们将呈现一些分形类型的模式,作为我们在整个示例站点中使用的可重用组件。当然,您也可以在自己的工作中使用它们。
可重用组件(我们称之为控件,源于用户界面控件的概念)提供了许多好处:
- 更快的页面开发时间
- 更快的错误修复
- 增加一致性
- 更少的模式
- 更大的接受度
- 提高质量
我们将在本章后面详细讨论这些好处。
在第五章中,我们展示了一点内容,然后通过改变与内容相关的 CSS 以多种方式重用它。通过这种方式,我们得到了一个可以放在很多地方的积木。如前所述,我们称这些构建模块为控件。
定义控件
让我们更详细地定义一下什么是控件。控件是一个 HTML 片段,带有相关的 CSS,可能还有可重用和可配置的 JavaScript。配置可以包括更改控件的内容。让我们以最简单的控件 Label 控件为例:
<span title="A description of this label" class="labelControl anotherClass">I’m a fun label control</span>
我们将使用一点 PHP 把它变成可重用的东西。虽然我们选择了 PHP 来说明这些原则,但它们是非常基本的概念,几乎适用于任何服务器端语言。我们选择 PHP 是因为它是一种脚本语言,这使得描述概念比用面向对象的语言(如 Java 或 C#)更容易一些(尽管我们已经用这两种语言做了这种工作)。
现在,回到我们的控制。我们可以创建一个通用函数来构造我们的标签:
清单 10-1。标签控件背后的 PHP 函数
<?php function label($innerText, $titleText, $addClasses) { var $payload = '<span title=' . $titleText . ' '; $payload .= 'class="labelControl``'; if ($addClasses != null) { $payload .= ' ' . $addClasses; } $payload .= '">'; $payload .= $innerText . '</span>'; echo $payload;} ?>
注在 PHP 中,为“.”字符是字符串连接字符,在许多其他语言中与“+”字符具有相同的功能。类似地。= '运算符将运算符后面的字符串与运算符前面的变量的字符串值连接起来,并将结果赋给变量。的’。= '运算符在许多其他语言中相当于'+= '运算符,如 Java 和 C#。
为了在我们的页面上调用这个控件,我们将在 HTML 中希望放置标签的地方放置以下内容:
<?php label("I'm a fun label control", "A description of this label", "anotherClass"); ?>
正如您所看到的,label 函数是一个非常简单的字符串生成器,其中我们用函数的参数替换内部文本值。如果页面的构建者指定了额外的类,我们就把它们放进去。否则唯一的类就是labelControl
。echo 语句是 PHP 将函数输出发送到调用该函数的 HTML 的方式。
你可能会想,“这是一个如此简单的 HTML,为什么要大费周章地制作一个控件呢?”为什么呢?因为这样做,即使对于简单的代码(如标签)也有一定的优势:
- 代码一致性
- 更简单的更新
- 更大的可读性
- 明确的意图
代码一致性
现在,您已经锁定了一个可以在整个站点中使用的 HTML,它将具有一致的、可预测的结构。您不会让其他开发人员使用他们自己的特定(也可能是奇特的)模式。
更简单的更新
因为您已经将这些代码抽象为一个函数,所以您有了一个中心点,可以从这个点对整个站点进行更改。您还可以选择添加功能和结构。正如我们将在本章后面提到的,你可以分离 CSS 和 JS 来匹配每个控件。由于所有相关代码都在一个地方,这大大加快了更新速度。
更好的可读性
尽管在实例化控件时模糊了控件的 HTML,但您不必查看所有多余的东西。这种优势在更大、更复杂的控件中尤其有用。
明确意图
如果您为控件使用描述性的名称,那么您的意图对其他开发人员来说是清楚的——当您以后再次访问这些代码时也是如此。
提示因为这些名字不会被插入到 HTML 中,所以你可以使用更长的描述性名字,而不用担心增加页面的字节数(从而增加带宽的使用)。
既然我们已经设计了最简单的控件,让我们继续我们模式的分形部分:控件中的控件。
走向分形:案例研究
我们在其他地方提到过,我们在一个主要的电子商务网站上合作过(事实上,是世界上最活跃的五个电子商务网站之一)。更具体地说,我们为这个网站的框架团队工作。超过 20 个团队使用这个框架来加速他们的开发。我们提供了一切,从一般的页面布局意图到控件,从简单的链接控件到复杂的迷你应用。除了拥有一个真正伟大的开发团队和富有远见的领导之外,让这个框架成功的是分形设计模式。
虽然将重复的模式抽象为控件增加了极大的灵活性和速度,但使其指数化的是这些模式中的每一个都由更小的模式组成。比如说一个常见的电子商务控件,一个刻面导航窗格,比如图 10-2 所示。该控件允许访问者根据各种标准过滤搜索结果。
图 10-2。多面导航控件
对我们来说重要的是,这个控件是由更小的可重用控件组成的:链接控件、标签控件、复选框控件、折叠控件等等。因此,分面导航成为刚刚提到的迷你应用之一。此外,分面导航窗格本身是一个更大的控件中的组件,也就是说,侧面导航控件本身包含在整个页面布局控件中。图 10-3 显示了分面导航控件中的主要控件(大部分由其他控件组成)。
图 10-3。分面导航控件内的控件
这种看似无穷无尽的控件嵌套在控件中的方式在很多方面都有好处。最明显的是,您不必在每次需要元素已经是控件的控件时都重写代码。对于订阅我们框架的团队来说,他们的工作被大大简化了。他们能够编写一行代码并传递参数(通常以模型对象的形式在。NET MVC 框架)来获得一致的、像素完美的元素来构建新页面。
明显的好处包括:
- 更快的页面开发时间
- 更快的错误修复
- 增加一致性
我们将在以下几节中详细描述每种优势。
页面开发时间更快
我们看到制作页面的速度提高了(有一次从几个月到几天)。因此,我们的客户团队可以更快地将新产品发布到网站上,这让业务人员(以及其他所有人)都很高兴。
更快的错误修复
我们可以很容易地指出使用该框架的团队在哪里误用了它。在这个框架出现之前,看起来我们 20 多个客户团队中的每一个都擅长找到自己制造混乱的方法。当然,有时错误是我们的,通常是在一些我们从未预料到的边缘情况下。由于我们使用的是我们熟知的有限数量的组件,我们通常可以快速修复这些错误(通常在一天之内)。
增加一致性
随着框架开始被组织的开发团队采用,页面越来越相似。这种相似性使得消费者更容易找到产品和信息。这也使得开发团队更容易为业务人员设定期望;他们可能会说“你的新页面会看起来像这个现有的页面。”最后,因为我们与一个独立的设计组织(公司内的另一个部门,但独立于开发部门)一起工作,设计师们很高兴看到他们的设计被各种开发团队更多地使用。
然后,随着我们更多地与使用该框架的团队合作,我们开始看到我们没有预料到的好处:
- 更少的模式
- 更大的接受度
- 页面加载时间好于预期
- 提高质量
同样,我们将在接下来的章节中详细描述这些意想不到的好处。
更少的模式
我们真的开始把网站的每个方面都看作是可重复的模式,这让我们减少了模式。我们经常会发现彼此非常相似的设计,但是每一个都有自己专用的 HTML、CSS 和 JavaScript 支持。我们能够接近我们的利益相关者(商业利益,独立的设计团队,和使用框架的开发人员)并且问,“你能使用我们现有的控制而不是你自己的控制吗?”大多数时候,涉众并不知道现有的控制。因为我们只在自定义控件非常类似于框架控件时才问这个问题,所以涉众通常会同意。
更大的接受度
我们不能强迫人们使用我们的控制。我们的团队是作为一个与现有团队平等的团队组成的,所以我们没有特别的权力。因此,我们工作的一部分基本上是销售。我们最好的销售工具之一是让客户团队意识到快速制定他们的需求符合他们的利益,并且该框架提供了制作满足各种业务单位需求的新页面的最快方法(我们谈论的是一家拥有 80,000 多名员工的公司 web 开发团队中有许多商业利益)。此外,他们知道大型网站内的一致性是可取的,这为我们的销售对话提供了另一个切入点。页面加载时间好于预期
自然,我们期望页面加载时间有所改善,因为我们编写可重用组件的方式使得 HTTP 请求减少,JavaScript 和 CSS 得到更多的重用。然而,即使是框架团队的成员也对改进的程度感到惊讶。在一个案例中,我们看到页面加载时间下降到了客户团队使用框架之前的三分之一。如果你看看今天的网络,你可以找到 CSS 文件大于 1 兆字节的网站(每次我们找到一个,我们都摇头表示不相信,但它们确实存在)。通过开发一个可重用组件的框架,包括 8 个页面布局意图(本质上是包含所有其他组件的主组件)和 40 个左右在这些布局中使用的控件,我们实现了一个 gzipped 和最小化的 CSS 文件,大小只有大约 50 千字节。
提高质量
最后一个不太明显的好处是质量。因为我们能够将数千个页面(事实上是数万个页面)上的所有内容减少到不到 50 个控件,所以我们能够投入大量时间来使这些控件坚如磐石。我们关注可靠性和可用性。转而使用我们的框架的团队可以依赖每次都以相同方式工作的控件。此外,我们是少数几个在团队中有专门的质量小组的团队之一;对于每两个开发人员,我们有一个 QA 人员,每个 QA 人员都是一个有经验的 web 开发人员。然后,我们将框架记录为 API,为所有控件及其参数和设置提供清晰、有意义的名称。最后,我们提供了一个示例站点,展示了如何实现每个控件,包括每个控件的所有变体(我们称之为处理)。最后,每个控件都有一个到设计部门控件定义的链接。有了这个链接,我们的客户团队可以验证我们已经正确地实现了控件,并从设计团队那里获得关于如何使用任何给定控件的进一步指导。我们有时间完成所有这些工作,因为我们已经确定了需要支持的相当小的一组控件。
虽然我们很高兴地承认为我们在这个大公司的框架团队中的工作感到自豪,但重点不仅仅是自吹自擂。我们希望您从这堂历史课中学到的信息是,采用可重用组件,然后在每个页面上使用它们,可以为您的组织的业务和开发带来实质性的好处。
现在,我们已经提供了一个可重用框架如何工作的高级概述,让我们继续讨论我们发现可以进一步提高性能(页面加载时间和开发人员性能)的两个相关项目:(1)分离 CSS 和 JavaScript,以及(2)组合 CSS 和 JavaScript。
分离 CSS 和 JavaScript
本质上,我们采用了“先分后合”的策略来管理 CSS 和 JavaScript 文件中的复杂性。虽然这听起来很奇怪,但事实证明它非常有效。所以让我们继续讨论它是如何工作的细节。
结合分形概念,该团队还采用了一种模式,即制作与每个控件相关联的小型 CSS 和 JS 文件。例如,一个链接控件也有一个link.css
文件和一个link.js
文件。然后,我们将使用一个合并器(我们将在本章后面讲到)将这些文件与其他相关的 CSS 和 JavaScript 文件缝合在一起。
这种方法的最大好处在于缺陷解决和维护。通过能够影响单个代码库中的许多模式,我们使得修复缺陷和对我们的文件进行变更请求变得相对容易。通过分离 CSS 和 JavaScript 文件,我们不必在庞大的 CSS 和 JavaScript 组合中寻找相关代码。特别是当你在一个团队(或者多个团队,就像我们的例子一样)中编写代码时,很难预测他们会使用什么样的选择器来影响他们的 CSS/JavaScript。如果选择器是确定的,那么很容易找到一些东西,但是有些人会使用后代选择器,如清单 10-2 所示。
清单 10-2。后代选择器的例子
<style> .main div ul a { color: #999; } </style>
他们将使用如清单 10-2 所示的样式来定义 HTML 内容的外观,如清单 10-3 中的所示。
清单 10-3。后代选择器示例的 HTML 示例
<div class="main"> <p class="desc">This is a great idea </p> <div class="reasonsWhy"> <ul> <li><a href="reason1.html" class="theReasons">Reason #1</a></li> <li><a href="reason2.html" class="theReasons">Reason #1</a></li> </ul> </div>
在清单 10-2 和清单 10-3 中显示的 CSS 和 HTML 的关系是糟糕的,原因有很多。首先,由于在 CSS 中使用了后代选择器,它比预期的要慢。(正如我们在第三章中提到的,由于后代选择器导致渲染引擎遍历 DOM 树,它们总是比按 ID 或按类选择要慢。)然而,使调试变得非常困难的问题是,当出现错误时,试图找到那个选择器。考虑一个包含在单个文件中的大量 CSS。你要读多少规则才能找到正确的?你有多经常阅读一个规则,因为它与许多其他规则非常相似,而没有意识到它实际上是你试图寻找的问题的匹配?那种事情耗费大量时间,非常令人沮丧。
但是,仅仅因为这是一个坏主意,并不意味着队友可能不会这样做。所以为了避免整个问题,即使你的队友使用的选择器很差,也要使用小的 CSS 文件,只包含你需要的特定组件的规则。至少这样您就有机会(因为代码应该少得多)在梳理代码时快速找到正确的选择器。此外,您可以对您的 CSS 首选项吹毛求疵,无论是您排序非破坏性规则(不影响特异性的规则)的方式,还是出于语义原因使用特定的父/子结构。
最后,对于大的 CSS 文件,当紧急情况出现时,团队可能很快失去纪律(他们总是这样),并且以后重新安排文件变得不可行。一旦达到这一点,您就不得不不断地维护这个庞大的文件。对于较小的文件,错误更容易被发现,修复也更容易,但是你必须有将代码分成小块的纪律。一旦你做了几次,看到了好处,你就会很容易找到规律。
结合 CSS 和 JavaScript
在开发时制作大量小文件很好,但在运行时却是一场灾难。那你是做什么的?你把所有的小文件合并成一个大文件,然后和每一页一起发送。
有很多最小化和合并文件的解决方案。对于我们的电子商务示例,我们使用了 Google Code 项目的 Minify 实用程序。它的标语是:“按需组合、缩小和缓存 JavaScript 和 CSS 文件,以加快页面加载速度。”你可以从[
code.google.com/p/minify/](http://code.google.com/p/minify/)
下载
注意Minify 工具只对 PHP 有效。您可以为其他语言找到类似的实用程序。例如,雅虎的 YUI 压缩器(在 http://developer.yahoo.com/yui/compressor/的有售)就是用 Java 编写的。
Minify 实用程序具有以下优点:
- 它将多个 CSS 或 JavaScript 文件合并并缩小到一个下载中。
- 它使用道格拉斯·克洛克福特 JSMin 库的增强端口和自定义类来缩小 CSS 和 HTML。
- 它缓存服务器端(
files/apc/memcache
)以避免做不必要的工作。 - 当浏览器拥有最新的缓存副本时,它会以 HTTP 304(未修改)响应进行响应。
- 大多数模块都是按需延迟加载的(304 个响应使用最少的代码)。
- 它会自动重写组合 CSS 文件中的相对 URIs,以指向有效的位置。
- 在启用缓存的情况下,Minify 能够在功能中等的服务器上每秒处理数百个请求。
- 对于内容编码,它使用基于请求头的 gzip。因为缓存允许这样做,所以它比 Apache 的 mod_deflate 选项更快地提供 gzip 文件!
- 它为大多数组件提供了测试用例。
- 它允许轻松集成第三方小型设备。
- 它有单独的实用程序类用于 HTTP 编码和缓存控制。Minify 的工作方式是将 CSS 和 JS 文件分别作为查询参数发送给 PHP 脚本。以下代码行显示了一个 CSS 示例:
<link type="text/css" rel="stylesheet" href="/min/b=css&f=reset.css,1140. css,mainNavDropDown.css,mainHead.css,breadcrumbs.css,sidebox.css,productStack.css,base. css,footer.css" />
以下代码行显示了一个 JavaScript 示例:
<script href="/min/f=common.js,link.js,mainNavDropDown.js" ></script>
只需将这些代码行放在与文件相同的位置。当然,我们知道 CSS 在顶部最好,JS 在底部最好,但是你有充分的灵活性来选择你认为最好的地方。
组合的唯一缺点是它会使用 Firebug 或类似的浏览器工具进行调试变得更加困难。因为最小化(删除空格和缩短标识符)和因为所有的文件被合并,阅读代码可能是一个真正的痛苦。为了在开发过程中避免这个问题,我们使用 CSS 和 JavaScript 文件的独立引用。清单 10-4 显示了一个例子。
清单 10-4。CSS 文件的独立链接
`<!--
--> `当我们调试时,我们删除单个链接周围的注释,并在组合链接周围添加注释,如清单 10-5 所示。
清单 10-5。链接到一个组合的 CSS 文件
`<!--
--> `这种安排给了我们更容易调试的代码。当然,当一个页面进入生产阶段时,我们会删除单独的链接。
总结
本章讲述了分形或递归容器模式,即在控件中放置控件,然后将这些控件放置在其他控件中,以此类推,直到您可以考虑的任何深度。我们还展示了一个来自我们自身经验的案例研究,以展示使用这种方法对业务和开发团队的好处。最后,我们讨论了在开发时将 CSS 和 JavaScript 文件分成小块并在运行时将它们组合起来的细节。
在接下来的几章中,我们将展示这种方法的一些具体例子。我们将从一些更简单的控件(链接和按钮)开始,然后是表格、标签和产品列表。一路上,我们将继续讨论在控件中使用控件的细节,因为更复杂的控件包含更简单的控件。我们将展示的代码和展示其工作原理的示例站点并不像我们在案例研究中提到的站点那样复杂,但它足以让您开始更好地控制站点的复杂性。
十一、链接控制
您可能会想,我们真的需要一个链接控件吗?编写一行 HTML 代码来完成这项任务不是更容易吗?虽然我们喜欢在简单的东西可以工作的时候不使用复杂的东西,但是我们也知道我们可以通过制作链接控件获得巨大的好处。首先,我们可以提高网站的可用性。另一方面,我们可以为我们自己和我们的团队大大提高开发人员的绩效(我们经常称之为开发速度)。
和我们所有的控件一样,我们创建了一个函数,然后可以在 HTML 中调用它。该函数为链接插入正确的 HTML。我们将包括一些参数,用于控制链接的各个方面(内部与外部链接、电子邮件链接等等)
虽然我们的方法可能看起来过于复杂,但我们将隔离这种复杂性,以便我们的队友可以获得一致样式的链接,而不必与复杂性纠缠。正如我们在第十章中提到的,在时间允许的情况下,将功能隔离在一个控件中可以让你在不中断开发团队的情况下极大地改进那个功能。当设计部门决定链接必须看起来不同时,你可以在一个地方改变它们,而不是改变你网站上的每个链接。对我们来说,这是一个巨大的好处,因为我们讨厌重复的(即无聊的)任务,这种搜索和替换操作几乎总是会产生错误。(我们似乎总是会错过一些,不是吗?)注意我们想指出一个经常被忽视的关于链接的事情:没有链接,网络就不会存在。众所周知但通常不会想到的是,HTTP 代表超文本传输协议。超文本的本质是在一个不断扩大的信息网中把一点内容链接到另一点内容。如果没有不起眼的 link 元素,我们就不会有万维网,也不会有工作(或者至少我们会有不同的工作)。因此,看似微不足道的链接元素实际上是我们许多生活的基础。
所以,事不宜迟,让我们看看链接控件是如何工作的,它给我们带来了什么好处。
功能
让我们考虑一下我们希望我们的链接控件做什么。当然,最起码要生成一个带有href
属性的锚标记和一个描述链接的字符串,如下面的 HTML 元素所示:
<a href="somepage.html">descriptive text</a>
如果这就是控件所做的一切,我们就不会在这么简单的事情上浪费我们(或你们)的时间。相反,我们将使我们的链接控件提供以下选项,从而证明我们开发它所花费的时间是值得的:
- 设置
href
属性和描述性文本,如果其中一个未设置,则抛出异常。让我们面对现实:没有这两项,链接就不是链接。 - 添加 CSS 类。
- 添加一个 ID。
- 添加链接类型:内部、外部、电子邮件或帮助。
- 设置工具提示消息。
- 控制链接是输出到页面还是作为值返回给另一个函数。
- 当我们检测到更有能力的浏览器时,获得渐进增强。
这还差不多。现在,我们需要一个灵活的控件,允许我们处理许多用例。此外,正如我们前面提到的,我们可以让一个开发团队分享这种生产力的好处,同时确保整个网站的一致性。这些也是重要的要求。现在我们知道了一些控件的要求,让我们写一些代码。
我们将再次使用 PHP,但是您也可以轻松地用 Java 或。NET 或者任何人们用于 web 开发的编程语言。基本上,代码的核心是一个函数,它构建一个字符串并将其输出到页面,或者将该字符串返回给另一个函数。
当您阅读下一个清单时,考虑我们插入的data-link-type
属性。这是我们创建的一个自定义属性,用来传递 CSS 规则使用的数据。(如果你想往前跳,清单 11-6 展示了我们如何处理这个定制属性。)虽然它是一个自定义属性,但 data-link-type 也遵循 HTML5 规范3.2.3.8 将自定义的不可见数据嵌入 data-属性*。
表 11-1 描述了mLink
功能的参数。
清单 11-1 显示了我们的链接控件的 PHP 代码。
清单 11-1。我们的链接控件的 PHP 代码
<?php function mLink($text, $href, $type, $class, $echo, $id, $tooltipMessage) {
`// A link with an href attribute and a way to describe what is about to
// happen seems pretty important, so we’ll make it mandatory. While
// there's cases for omitting these, we'll keep this code clean and
// handle those other cases in a different manner.
// We'll use a try-catch block so that we can do
// something if we don't get the values we require.
try {
if (($text == NULL || \(text =="") || (\)href == NULL || $href =="")){
throw new Exception("At least one Link is missing descriptive text and/or an href");
}
} catch (Exception $e){
// We've chosen to display our exception in eye-jarring colors that
// really stick out. It makes for immediate feedback as you develop,
// but it can backfire on you. Choosing to do it in the DOM is risky
// as it can be seen by visitors if bad data are passed to your live
// site. You have the option here of choosing how you learn about the
// exception. You could just as easily write this to a log or send it
// to a browser that has a console (Chrome, Firefox with the Firebug
// plugin, IE8+), by un-commenting the following line:
// echo '`
我们已经编写了一个 jQuery 插件,用于在generatedcontent
功能不可用时(也就是说,当伪:after
和:before
类不可用时)处理帮助工具提示功能。我们通过插入一个div
元素并给它分配一个tooltipMessage
类,使用插件来近似:after
功能,我们将它添加到通过 CSS 处理工具提示的相同规则中。然后我们将添加来自data-tooltip-message
属性的值,这个值是用链接控件设置的。
然后我们需要做的就是将我们的工具提示插件包装在一个if
语句中,当generatedcontent
功能不可用时运行该语句。通过插件,我们将工具提示div
附加到任何具有data-tooltip-message
属性的锚点上。
总结
在这一章中,我们建立了一个链接控件,可以在我们的站点上使用。这样做有以下好处:
- 确保整个站点的一致性;这有助于访问者导航,并使网站更具吸引力。
- 提高开发人员的生产力(或者速度,如果你喜欢的话)。
- 使用封装,这样开发人员就不必在每次需要比链接更多的链接时编写自己的解决方案。封装进一步提高了速度,减少了出现错误的机会。
为了制作链接控件,我们创建了三个部分。首先,我们创建了一个函数,它将网页中的代码替换为最终的 HTML,并发送到用户的浏览器。该函数接受几个参数,开发人员可以使用这些参数向链接添加一些可选功能。该函数还支持五种不同的链接(通过其类型参数)。如果我们的开发团队需要另一种链接,我们可以添加功能,而不是强迫我们的团队创建自己的解决方案(同样,出于一致性和质量的原因,我们希望避免这种情况)。
其次,我们创建了许多 CSS 规则集来设计链接的样式。这种样式不仅包括控制链接的外观,还包括定位和样式化工具提示文本,除了最简单的链接。为了与渐进增强的理念保持一致,我们为支持 CSS3 动画的浏览器的链接添加了一个动画悬停,并为旧浏览器添加了一个标准悬停。
第三,我们使用 JavaScript 为不能通过 CSS 显示工具提示的浏览器添加了故障转移功能。我们不想失去我们的工具提示(特别是在帮助链接上),所以我们增加了一点渐进式的增强,以确保每个人至少都能得到工具提示,即使他们没有得到动画或其他花哨的效果。
同样,对于一个链接来说,这可能看起来需要做很多工作。但是想想普通电子商务网站上的链接数量。他们不应该看起来和工作都一样吗?我们是这样认为的,这就是我们实现这些目标的方式。
十二、边栏框控制
现在,您已经看到了相对简单的链接控件,让我们看看控件中的控件以及这种结构提供的开发人员生产率。侧框(或插图)是大多数多栏网站的主要内容。边栏通常位于站点主要内容区域的左侧或右侧。这个控件非常通用,因为它可以包含许多不同类型的信息:链接、信息标注、导航、调查以及其他各种信息。图 12-1 展示了一个典型的餐边柜,旨在突出展示我们商场的新品:
图 12-1。一个列有新产品链接的边栏
内容
尽管 sidebox 控件有许多用途,但无论其内容如何,它总是具有某些功能,特别是页眉和边框。让我们通过指定这两个特性来开始开发我们的控件(见图 12-2 )。
图 12-2。空白嵌入控件
sidebox 的结构非常简单,如清单 12-1 中的所示。
清单 12-1。边栏框的 HTML
`
`正如您所看到的,它只是一个 nav 元素,作为标题(一个h1
元素)和其他内容(在div
元素中)的容器。我们指定了一些类来控制 sidebox 控件的外观,接下来我们将讨论这些。
造型
边栏框控件的 CSS 非常基本,除了我们给H1
添加了一些阴影,给边栏框增加了一点视觉吸引力。我们已经使用了我们的老朋友,:after
和:before
伪类(在h1
元素上)来实现这个效果。为了避免另一个 HTTP 请求,我们将图像编码为 base64 并包含在 CSS 中。
我们将分别描述每个类和伪类,以避免清单过于庞大而难以理解。清单 12-2 显示了.sideBox
类,它指定了底部边距(防止内容撞到标题)。
清单 12-2。美国。侧盒类
.sideBox { margin-bottom: 10px; }
清单 12-3 显示了.sbH1
(侧框 H1 的简称)样式,它指定了H1
元素的许多属性。除了像字体大小和颜色这样单调的属性之外,我们还为边框控件指定了圆角。指定圆角占据了清单的大部分,因为我们必须让它适用于所有支持圆角的浏览器。
清单 12-3。. sb h1 类
.sbH1 { font-size: 16px; color: white; padding: 3px; text-align: center; letter-spacing: -0.05em; -webkit-border-top-right-radius: 5px; -webkit-border-bottom-right-radius: 0; -webkit-border-bottom-left-radius: 0; -webkit-border-top-left-radius: 5px; -moz-border-radius-topright: 5px; -moz-border-radius-bottomright: 0; -moz-border-radius-bottomleft: 0; -moz-border-radius-topleft: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; border-top-left-radius: 5px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; position: relative; margin: 0; }
清单 12-4 显示了.sbH1:before
伪类。此类指定图像的位置,该图像在包含标题的框的左侧提供圆形阴影的外观。它还提供图像,作为在base64
中编码的数据块。
清单 12-4。sb h1:上课前
.sbH1:before { content: ""; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAZCAMAAADKUMQKAAAAGXRFWHRT b2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+dpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0 YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAg ICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4g PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4 bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUu Y29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNl UmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wOkNyZWF0ZURhdGU9IjIw MTItMDQtMjhUMTA6MzA6NDYtMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDEyLTA2LTE4VDIxOjE1OjU1LTA1OjAwIiB4bXA6 TWV0YWRhdGFEYXRlPSIyMDEyLTA2LTE4VDIxOjE1OjU1LTA1OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgeG1wTU06SW5zLmlpZDoyMjQyNzc2NUIxQUYxMUUxOEJFODlBMDkxM0UyQ0FDNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDoyMjQyNzc2NkIxQUYxMUUxOEJFODlBMDkxM0UyQ0FDNCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5j ZUlEPSJ4bXAuaWlkOjIyNDI3NzYzQjFBRjExRTE4QkU4OUEwOTEzRTJDQUM0IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlk OjIyNDI3NzY0QjFBRjExRTE4QkU4OUEwOTEzRTJDQUM0Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4 bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+ADj9TgAAADBQTFRF/v7+7e3t/Pz83d3dzMzM9/f3u7u79fX1+vr61NTUrKys 4eHhw8PD5eXl8fHxsrKy+VpLHQAAAF1JREFUeNp8TtsWgDAI0nRry13+/29jtE5v+QIHEZR0CEfPQoxNKshatdpHAcluFCaE 5QDGAF7ZQxPRFGH/+Plnfu4nC5DfjM1vD+LXmbixTrYscPNl2392uuQWYABfdwN7BXQddQAAAABJRU5ErkJggg==); width: 7px; height: 25px; position: absolute; z-index: 2; top: 0; right: -7px; }
清单 12-5 显示了.sbH1:after
伪类。这个类指定图像的位置,该图像在包含标题的框的右边提供圆形阴影的外观。如果你想知道为什么我们在这里使用 SVG 图像,这是为了创建一个特殊的阴影效果。我们可以尝试使用不同的边框半径设置,但不能保证它能在任何给定的浏览器上工作。此外,该方法使用的带宽不会超过使边界半径技术在尽可能多的浏览器上工作所需的所有属性。由于 SVG 更有可能在更多的地方工作,所以它成为获得所需外观的最佳选择。在阅读清单时,您可以忽略 SVG 数据,因为它是以 base64 编码的数据块,无论如何也不是人类可读的。
清单 12-5。sb h1:下课后
.sbH1:after { content: ""; background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAZCAMAAADKUMQKAAAAGXRFWHRT b2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+dpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdp bj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0 YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMCAgICAg ICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4g PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4 bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUu Y29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNl UmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wOkNyZWF0ZURhdGU9IjIw MTItMDQtMjhUMTA6MzA6NDYtMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDEyLTA2LTE4VDIxOjE2OjEwLTA1OjAwIiB4bXA6 TWV0YWRhdGFEYXRlPSIyMDEyLTA2LTE4VDIxOjE2OjEwLTA1OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgeG1wTU06SW5z dGFuY2VJRD0ieG1wLmlpZDoyMjQyNzc2OUIxQUYxMUUxOEJFODlBMDkxM0UyQ0FDNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1w
LmRpZDoyMjQyNzc2QUIxQUYxMUUxOEJFODlBMDkxM0UyQ0FDNCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN0YW5j ZUlEPSJ4bXAuaWlkOjIyNDI3NzY3QjFBRjExRTE4QkU4OUEwOTEzRTJDQUM0IiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlk OjIyNDI3NzY4QjFBRjExRTE4QkU4OUEwOTEzRTJDQUM0Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4 bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+lSD+3wAAADBQTFRF+fn529vb/v7+8fHxxMTE9fX1sLCw+/v7vb297Ozs0dHR 5ubm4uLizc3N1tbW6enp6eXkiwAAAF9JREFUeNqMzksSwCAIA9AgWj/Qev/bltBx39UbJWJQSuloihBtGnhUE6pb7hDqq6L0 Nv0edEulak+9EOOjr/FLW8yf97kvnZL7M5j/RoA92tejY/rKnjGgbJTGxSvAAHW1A9LlrBDQAAAAAElFTkSuQmCC); width: 7px; height: 25px; position: absolute; z-index: 2; top: 0; left: -7px;
} .sbBody { -webkit-border-top-right-radius: 0; -webkit-border-bottom-right-radius: 3px; -webkit-border-bottom-left-radius: 3px; -webkit-border-top-left-radius: 0; -moz-border-radius-topright: 0; -moz-border-radius-bottomright: 3px; -moz-border-radius-bottomleft: 3px; -moz-border-radius-topleft: 0; border-top-right-radius: 0; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; border-top-left-radius: 0; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; border: 1px solid #C62125; padding-bottom: 10px; border-top: 0; font-size: 13px; box-shadow: 0 2px 3px #AAA; }
提示将内容与演示分离
我们想提到一个编程范例(一种模式),我们已经在其他地方取得了巨大的成功:MVC。MVC 代表模型-视图-控制器(网站的一种常见设计模式)。MVC 在分离内容和表现方面做得很好。我们在一个项目中相遇,这个项目的任务是将一个非常大的网站从 ASP.NET 和 webforms 转换成微软的 MVC。webforms 代码中有太多的业务逻辑纠缠在一起,以至于我们放弃了转换它的尝试,并重新开始新的设计。这个激进的步骤让我们将表示放在视图代码中(它所属的地方),并将业务逻辑放在模型中(同样,它所属的地方)。遵循 MVC 模式的最佳实践,我们有非常“瘦”的控制器,基本上只是提供了一些进入控件的入口点。MVC 的口头禅是“胖模型,瘦控制器”
每个对照组都有一些“治疗”,这些治疗或多或少是对照组的微小变化。例如,我们有一个没有标题的 sidebox 控件。该版本的控件在 sidebox 控件的 controller 类中有自己的方法。控制器所做的只是将模型(保存数据)传递给另一个类,该类呈现构成控件的 HTML 元素。呈现类是数据不可知的。只要数据符合某些参数(大多数情况下,它必须存在,但有些控件需要列表或其他特定的数据结构),呈现类就会将数据转换成 HTML。
如果控件有特殊的特性,我们将这些特性的逻辑移到模型类中。一个主要的例子是选项卡控件,它呈现了一系列的选项卡。选项卡可以按多种方式排序:字母顺序、倒字母顺序、原始顺序(最常见)等等。该模型使用了一个枚举来告诉呈现类如何对选项卡进行排序。将这种逻辑移到模型中会让呈现类(以及整个控件)变得像谚语中的石头盒子一样愚蠢。真正愚蠢的控制让我们远离试图理解业务逻辑。
MVC 的另一个伟大的特性是能够使用局部视图(通常称为“局部视图”)。通过局部视图,指定控件外观的视图可以包括处理父视图的部分内容的局部视图。我们将为 sidebox 控件做一些非常类似的事情,因为这样做是我们让sidebox
函数与内容无关的方式。通过 partials,sidebox 控件可以处理任何类型的内容。我们将在下一节“函数”中讨论这个问题
这听起来好像我们试图避免帮助业务团队。实际上反过来才是正确的。业务和开发关注点的很好的分离让我们完成更多的工作,并且它也使业务团队能够完成更多的工作。我们提供了大量的 API 文档和一个完整的工作示例站点,其他团队可以在那里看到运行中的控件。我们甚至为每一个对照加入了每一种可能的变异(即每一种治疗)。这是一个相当不错的地方。
许多人建议将表示与内容分离,并将业务逻辑放在 web 代码之外。我们的经验明确证实了这种方法。
功能
我们讲述了我们如何将内容与演示分离的故事,以便更容易理解我们将要做的事情。我们将在用于呈现这个版本的 sidebox 控件的函数中遵循这些原则。只要我们得到了头的值和内容的局部视图(定义了如何呈现自己的内容),我们根本不关心这两个参数中有什么。这个 sidebox 控件可以呈现许多不同种类的内容,这使得它非常有用。
注意呈现内容的分部可以包含一个或多个段落。因此,sidebox 控件可以像在电子商务网站上一样方便地在博客上使用。同样,当演示不需要关心内容时,控件的有用性会大大增加。
表 12-1 显示了sidebox
功能接受的参数。
在我们的例子中,我们将通过另一个函数呈现局部视图。不同的平台以不同的方式处理偏音。PHP 和其他脚本语言倾向于使用另一个函数(尽管 PHP includes 可以只包含普通的 HTML)。面向对象的平台,如微软 MVC(使用多种语言)和 Spring(使用 Java)倾向于使用一个类作为局部视图。清单 12-6 显示了边框控件的入口点。
清单 12-6。Sidebox 控制入口点
<?php function sidebox($title, $content, $class){ $path = $_SERVER['DOCUMENT_ROOT']; $sbTitle = $title; $sbContent = $path.$content; // The path to the partial view $classadd = ($class == null ? "" : $class); include $path."/includes/sideBox.php"; } ?>
sidebox
函数的第一行定义了一个包含站点根的变量。接下来的三行创建变量来保存标题、向我们显示内容的局部视图的路径以及附加的类(如果有的话)。最后一行指定在哪里可以找到 sidebox 控件的 HTML。换句话说,最后一行定义了控件的主视图。主视图保存标题,然后调用分部视图(在$content
参数中指定)来呈现内容。
注意在这种情况下,$content 参数的值将是 new_to_store.php,它是定义 sidebox 内容的文件。我们将在本章的后面讨论这个文件。
清单 12-7 显示了sideBox.php
(我们将它放在了includes”
目录中)。
清单 12-7。sidebox.php
的内容
`
`如您所见,我们将标题和 CSS 类名传递到 HTML 中的适当位置。然后我们还包括进入控件的内容的路径。通过此方法,sidebox 控件可以包含任何内容,而无需控件中的内容逻辑。这种方法也给了我们巧妙处理内容的方法来帮助加速开发,就像我们在图 12-1 中处理边栏内容一样。这里我们将使用第十一章中的链接控件和一个for
循环来为我们完成工作。为此,我们将创建一个函数来定义内容,然后使用另一个函数来呈现内容。
清单 12-8 展示了我们如何在一个外部数组中创建一组数组内容。我们在一个名为new_to_store.php
的单独文件中完成。
清单 12-8。 new_to_store.php
,其中包含了样品侧框控件的内容
<?php $newToStoreArray = array( array("Bob's Back Cream", "javascript:fakelink(this);", NULL, "sbA-L2"), array("Giant Tentacle Hands","javascript:fakelink(this);", NULL, "sbA-L2"), array("Cricket Gun","javascript:fakelink(this);", NULL, "sbA-L2"), array("Ax of Deprecation","javascript:fakelink(this);", NULL, "sbA-L2"), array("Speller's Handbook","javascript:fakelink(this);", NULL, "sbA-L2"), array("Water Walking Shoes","javascript:fakelink(this);", NULL, "sbA-L2"), array("JetPack Beta","javascript:fakelink(this);", NULL, "sbA-L2"), array("Spaghetti Gun","javascript:fakelink(this);", NULL, "sbA-L2"), array("Inverted Speakers","javascript:fakelink(this);", NULL, "sbA-L2"), array("Righteous Anger Pills","javascript:fakelink(this);", NULL, "sbA-L2") ); echo buildLevel2List($newToStoreArray); ?>
清单 12-8 中的大多数只是在一个外部数组中构造了一组数组。我们以这种方式创建它,以模拟我们通常期望从数据库接收的数据类型。但是,对于示例站点,我们只使用静态数据,因为我们关注前端技术,不想因为处理数据库而使书籍变得杂乱。这些数据可以是 XML 或其他任何东西,只要你能写一个函数(或方法,如果你使用面向对象的语言)来解析。最后一行调用了rendering
函数。虽然不是严格的 MVC,new_to_store.php
基本上创建了我们的模型。在严格的 MVC 中,我们不会调用下一个函数;控制器会将模型传递给我们的渲染代码。
现在我们有了数据,我们将数据传递给另一个函数,该函数将构建 HTML(一个无序列表),作为示例 sidebox 控件的内容。我们可以在new_to_store.php
中包含 HTML 构建功能。然而,我们想把数据和表现分开。此外,我们可能有其他用途的函数,建立一个名单。出于这些考虑,我们决定将列表构建代码抽象成它自己的函数。清单 12-9 展示了创建一个 HTML 列表的函数。
清单 12-9。创建一个无序列表作为内容
`function buildLevel2List(\(linkArray){ \)StringBuilder .= '
- ';
- ';
\(StringBuilder .= mLink(\)linkArray[$i][0], \(linkArray[\)i][1], \(linkArray[\)i][2],
\(linkArray[\)i][3], "return");
\(StringBuilder .= '</li>'; } \)StringBuilder .= '
for ($i=0, \(size = sizeof(\)linkArray); $i < $size; \(i++) { \)StringBuilder .= '
return $StringBuilder;
}`
我们使用一个字符串构建算法来创建一个 HTML 元素集合,然后将它插入到一个更大的 HTML 元素集合中,构成 sidebox 控件。字符串构建提供了创建标记的最佳方式。为每个元素创建一个对象会导致更大的内存使用量和更慢的性能,但没有任何好处。这个观察也适用于严格面向对象的语言,比如 Java 和 C#。将 HTML 创建为字符串在我们目前遇到的所有语言中提供了最好的性能。(Jay 第一次了解到这个真理是在他为 Apache FOP 项目做贡献的时候,并且在这些年里反复验证了它。)
第一行创建一个无序列表(UL)元素的开始标记。然后for
循环遍历外循环。对于外部数组中的每个内部数组,for
循环创建一个li
元素。每个li
元素包含一个链接控件的实例(在第十二章“链接控件”中有详细描述)。为了给链接控件提供参数,我们在处理每个数据成员时单独选择我们需要的数据比特。
注意我们必须提供一个值“return”作为链接控件的最后一个参数。否则,链接控件会回显它自己的 HTML,把我们的列表弄得一团糟。这就是为什么 link 控件有最后一个参数来控制是直接回显它的输出还是将它的输出返回给另一个函数,就像我们在这里所做的一样。
现在我们有了一个边盒控件,它提供了极大的灵活性。虽然我们没有使用 MVC 框架,但我们在编写这个控件时考虑了这个模式,因为我们希望小心地将内容和表示分开。由于这种分离,我们现在可以从一个控件中创建任意数量的 sideboxeses,每个 sidebox 包含不同种类的内容。这省去了为每个边盒编写定制代码的麻烦,并避免了编写代码来处理每个边盒时出现的所有错误。最后,我们现在有了一个单一的地方,在这里我们可以对我们的 sidebox 控件进行改进或添加新功能。如果一个设计团队开始敲开大门,让我们包括没有标题的边盒,我们可以很快适应它们。此外,如果我们想办法让 sideboxes 性能更好或更健壮,我们可以在一组代码对象中进行这些改进,并在整个网站上看到好处。
如果我们没有提到制作和使用控件的最后一个好处,而不是为网站中每个页面的每个设计元素编写定制的 HTML 和 CSS,那我们就失职了:通过在另一个控件中使用一组控件,我们进一步扩展了我们的灵活性和健壮性。我们知道我们的链接控件是如何工作的,因为我们一直在使用它。同样重要的是,我们知道它是如何损坏的,如果它损坏了,我们也知道去哪里修理它。有没有一个 bug,不知道从哪里开始寻找它?我们当然有,我们讨厌这样。制作和使用控件可以让我们摆脱这个问题。
总结
在这一章中,我们提供了另一个在我们的示例网站上使用的示例控件,如果适合您的需要,您可以在自己的网站上自由使用。你可能需要扩展它一点,让它处理你的网站上所有不同种类的侧框(或者 insets 或者你想叫它们什么)。尽管如此,它还是提供了一个起点。
正如我们对所有控件所做的那样,我们展示了如何为控件定义 HTML、CSS 和处理程序代码。在我们的例子中,我们使用 PHP。您可以轻松地使用 C#或 Java 或任何其他可用于呈现网页的语言。类似地,我们不使用框架,但是你当然可以使用这个控件和我们在框架中提供的其他控件——例如 Spring 或 Microsoft MVC。
最后,我们想提醒你把你的内容(也就是数据)和你的演示分开。你的控件越是与内容无关,你能用它们做的事情就越多。当然,有时一个控件只有在存在某种数据时才有意义。在后面的章节中,当我们描述我们的产品堆栈控制时,我们将向您展示如何处理这个问题。
十三、按钮控制
这是另一个看似简单的 HTML 代码,可以制作成一个控件。然而,尽管这看起来像是一个奇怪的投入精力的地方,但是这样做在一致性、灵活性和开发速度方面是有回报的。我们将坚持分形设计模式。也就是说,我们将对每种按钮使用(实际上是重用)相同的 HTML,在服务器端使用布尔值来标识要显示哪种按钮,并通过动态设置 CSS 类来创建表示变体。
注我们通常称控制的每一个变化为“处理”例如,一个按钮是一个按钮,但两个不同的按钮在外观上可能会有很大的不同。这个概念很像房子上的窗户处理。窗户是窗户,但百叶窗和窗帘给窗户带来了截然不同的外观。
我们展示了一系列按钮来说明这种方法。然而,与链接控件一样,几乎任何模式都是可以想象的。在我们的样本现场,我们将采用图 13-1 中所示的处理方法。
图 13-1。按钮控制上的变化(处理)
您可以在[
clikz.us/BookExamples/buttonControl.php](http://clikz.us/BookExamples/buttonControl.php)
查看这些按钮(也是全彩色的)并与之互动。在网上的例子中,你可以看到我们为我们的按钮加入了一些有趣的交互。也就是说,这个按钮看起来是在悬停时按下的,或者是有一种内在发光的搏动,这取决于我们使用的治疗方法。这些动画纯粹是为了吸引眼球,但它们提供了一种利用新浏览器功能和卓越体验的好方法。我们也将确保为使用旧浏览器的访问者提供更传统的悬停效果。一如既往,我们支持渐进式改进,并努力确保所有访问者获得他们的浏览器所能提供的最佳体验。
图 13-1 显示了几种类型的按钮。我们简单看一下区别。我们已经定义了一个名为 Primary 的按钮类。我们可以很容易地(也许更具描述性地)称它为蓝色按钮。然而,如果更新的需求要求改变颜色,这个描述性的名称将会是一个问题。那么我们要么查看一个断开的按钮名称,要么替换一堆代码来纠正它。因此,我们发现使用描述目的的术语要比描述外表的好得多。在这种情况下,如果我们也有一个绿色按钮,我们可以称之为辅助按钮,或者它可以是特定于一个动作的,因此被称为购买按钮。在我们的例子中,所有的按钮都属于主系列。
按钮类型
我们只有主要按钮(与次要按钮相反,如果我们有更多的信息,我们可能会使用次要按钮),但我们有几种不同的处理方法,正如我们在本节的其余部分所描述的。
初级
让我们从默认按钮开始。让主系列中的所有按钮都从这个默认按钮派生出来,可以让我们有一点 CSS 抽象,如果需要的话,我们可以为特定的按钮进行扩展。这种处理包含悬停交互,其他按钮处理可以按原样使用或覆盖。
图 13-2。初级按钮处理
主图标
标题说明了一切。我们使用基本的主按钮,但添加了一个图标。我们将在这一章的后面讨论它是如何工作的。
图 13-3。初级用图标按钮治疗
初级玻璃
对于这种处理,我们在主按钮上创建一个玻璃效果。为了达到这个效果,我们使用了 CSS 渐变。如果你觉得在截图中很难看到玻璃效果(我们有),请访问我们的示例网站, [
clikz.us/BookExamples/buttonControl.php](http://clikz.us/BookExamples/buttonControl.php)
图 13-4。初级玻璃按钮处理
原生阴影
主要的阴影按钮处理看起来非常类似于主要的玻璃按钮处理,但是我们通过使用一种不同的技术来创建效果:白色的嵌入阴影。这需要比渐变少得多的代码,并允许一些复杂渐变不允许的 CSS 动画选项。我们实际上使用阴影方法来生成悬停时的悸动效果。
图 13-5。初级阴影按钮治疗
初级开始
在这里,我们通过定义一个开始形状(即形状在按钮之外)来增加一点视觉趣味。我们使用:before
伪类来创建外部形状,以便我们的标记保持干净。
图 13-6。初级开始按钮处理
初级围棋
当我们需要一个小按钮来进行搜索或其他界面元素变得拥挤的地方时,主要的 Go 按钮处理就很方便了。我们可以使用border-radius
属性生成圆;我们将在本章的后面讨论代码。
图 13-7。初级 Go 按钮处理
初选开始
主要的 Go Outset 按钮处理是一个 Go 按钮,它有一个由:before
伪类定义的外部图像。当我们需要一个比基本的 Go 按钮更有存在感的小按钮时,这很有用。
图 13-8。初级 Go 开始按钮治疗
编码按钮控件
让我们检查按钮控件背后的代码。现在可能已经很熟悉了,因为它使用了以前控件中使用的相同技术。我们在这里使用分形设计模式,所以我们用一些条件来制作一个字符串。
表 13-1 描述了price
功能的参数。
清单 13-1 显示了 PHP 函数,它是我们按钮控件的核心:
清单 13-1。主按钮功能
<?php // Our main button function. function button($text, $href, $id, $title, $type, $class, $icon, $iconClass, $htmlElement) { $htmlEl = ($htmlElement == "button" ? "button" : "a");
`// Start string that contains the HTML output with
// the option to use an anchor tag or a button tag.
$output = "<" . \(htmlEl;
// Put in our href attribute.
\)output .= " href='" . \(href . "'";
// Get a base class of button.
\)output .= " class='button";
// If there's a type declared, get the proper classes that give our
// different treatments.
\(output .= ((\)type != NULL || \(type != "") ? " " . getTypeClasses(\)type) : "");
// If addition classes are present pass them in. This ability allows
// us greater flexibility.
\(output .= ((\)class != NULL || $class != '') ? " " . \(class . "'" : "'");
// If a title is declared, add it.
If (\)title != NULL && \(title != "") {
\)output .= " title='" . \(title . "'";
}
\)output .= ">";
$output .= \(text;
if (\)icon == TRUE) {
\(output .= "<span class='buttonIcon";
if (\)iconClass != NULL && \(iconClass != "") {
\)output .= " " . $iconClass . "'>";
}
}
// Finish the string by adding the closing tag (either or ).
$output .= "</" . $htmlEl . ">";
echo $output;
}
// This function adds the appropriate classes based on the declared \(type.
function getTypeClasses (\)type) {
switch ($type)
{
case "primary":
return "primary";
break;
case "primaryGlass":
return "primary glass";
break;
case "primaryShadow":
return "primary shadow";
break;
case "primaryOutset":
return "primary outset";
break;
case "primaryGo":
return "primary go";
break;
case "primaryGoOutset":
return "primary go outset";
break;
default:
return "";
}
}
?>`
正如我们之前提到的,相同的目的可以在许多不同的语言中实现。我们只是碰巧在使用 PHP(我们不得不使用一些东西,我们发现 PHP 很容易阅读)。
我们在这里没有任何幻想。唯一看起来奇怪的部分是包含了按钮作为锚元素或按钮元素的能力。如果您需要在表单中放置一个按钮,您可能希望使用一个按钮元素(这样更容易附加submit
行为等等)。在表单之外,您可能希望使用锚定元素,因为这样的按钮与锚定元素具有相同的效果。
同样,您现在可能已经认识到了这种模式。为了实例化我们的按钮控件,我们在希望按钮出现在 HTML 中的位置调用带有所需参数的函数。清单 13-2 显示了创建按钮的代码。
清单 13-2。创建按钮
`
Primary Go Button
Primary Go Outset Button
`
清单 13-3 显示了插入到结果文档中的 HTML 标记。
清单 13-3。函数调用产生的 HTML】
`Primary
Primary Button with Icon
Primary Glass Button
Primary Shadow Button
Primary Outset Button
Primary Go Button
GOPrimary Go Outset Button
GO`
CSS
与往常一样,CSS 变得相当密集,因为所有的供应商前缀使事情能够在不同的浏览器中正常工作。有趣的部分是在 outlet 类中,我们使用了:before
伪类,结合了填充、偏移位置和z
-index 来获得 outlet 效果。基本上,我们通过使用:before 伪选择器生成一个元素,并使该元素在宽度、高度和边框半径上匹配包含它的按钮。然后,我们在四周添加了一个 5 像素的填充,这使得整个处理比按钮本身大 10 像素。为了使按钮在其包装内居中,我们添加了–5 像素的顶部和左侧值。索引值为-1 的z
将包装器放在按钮的后面,以便可以单击按钮。
我们喜欢的另一项技术是通过 base64 编码为我们的buttonIcon
规则集定义一个背景精灵,而不是一个指向精灵的链接。这是一种在 CSS 中缓存图像以备将来重用的好方法;我们已经在边盒控件中使用了它(见第十二章)。如果我们需要这个 sprite,我们给这个元素一个基类buttonIcon
,我们就有了它。这样,我们可以两全其美:我们避免了代价高昂的 HTTP 请求,并且图像被缓存。
最后,值得注意的是,生成主玻璃按钮需要相当多的 CSS,特别是如果它要在 Internet Explorer 9 中用额外的 SVG 编码看起来很好的话。相比之下,主要的阴影按钮效果只用了一小部分代码就实现了类似的外观。我们展示了这两种方法,让你意识到它们的区别。我们一般建议使用阴影技术,除非客户绝对坚持玻璃效果(有轻微的视觉差异,每个人都见过客户痴迷于此类事情)。
和其他控件一样,CSS 看起来有点大。为了可读性,我们将 CSS 分成多个清单,尽管在我们的示例站点中它是一个单独的代码块。清单 13-4 显示了第一种默认的按钮样式,它适用于所有的按钮处理。
清单 13-4。第一个默认按钮样式
/* * * Default button styles* * */* .button, .button:visited { padding: 4px 15px 5px 15px; -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-color: #1f81dd; color: white; display: inline-block; text-shadow: 0 0 4px rgba(0, 0, 0, 0.6); -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.6), 2px 2px 3px rgba(0, 0, 0, 0.4); -moz-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.6), 2px 2px 3px rgba(0, 0, 0, 0.4); box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.6), 2px 2px 3px rgba(0, 0, 0, 0.4); text-decoration: none; font-size: 14px; position: relative; border: 1px solid rgba(0, 0, 0, 0.3); }
清单 13-5 显示了当访问者的鼠标停留在按钮上时,定义按钮外观的 CSS。
清单 13-5。默认按钮悬停样式
.button:hover { color: white;
background-color: #24c61c; text-decoration: none; text-shadow: 0 0 4px rgba(255, 255, 255, 0.2); -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.6), 1px 1px 1px rgba(0, 0, 0, 0.4); -moz-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.6), 1px 1px 1px rgba(0, 0, 0, 0.4); box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.6), 1px 1px 1px rgba(0, 0, 0, 0.4); top: 1px; left: 1px; -webkit-animation: shadowThrob 1s infinite; -moz-animation: shadowThrob 1s infinite; -o-animation: shadowThrob 1s infinite; animation: shadowThrob 1s infinite; }
清单 13-6 显示了当访问者的浏览器不支持动画时应用的样式规则。在这种情况下,我们只需改变背景颜色。
清单 13-6。不支持动画的浏览器的默认悬停风格
.no-cssanimations .button:hover { background: green; }
清单 13-7 显示了定义主要按钮处理外观的第一条规则。跳过 SVG 块就行了。这是 base64 编码的数据,不适合人类阅读。
清单 13-7。初级按钮处理的第一法则
*/** * * Primary button* * */* .button.primary { background: #2c81da; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdm lld0JveD0iMCAwIDEgMSIgcHJlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLX VjZ2ctZ2VuZXJhdGVkIiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeT I9IjEwMCUiPgogICAgPHN0b3Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzJjODFkYSIgc3RvcC1vcGFjaXR5PSIxIi8+Ci AgICA8c3RvcCBvZmZzZXQ9IjQ4JSIgc3RvcC1jb2xvcj0iIzViYWRmZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcC BvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMyYzgyZGEiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbn Q+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdG VkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top, #2c81da 0%, #5badff 48%, #2c82da 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #2c81da), color-stop(48%, #5badff), color-stop(100%, #2c82da)); background: -webkit-linear-gradient(top, #2c81da 0%, #5badff 48%, #2c82da 100%); background: -o-linear-gradient(top, #2c81da 0%, #5badff 48%, #2c82da 100%); background: -ms-linear-gradient(top, #2c81da 0%, #5badff 48%, #2c82da 100%); background: linear-gradient(top, #2c81da 0%, #5badff 48%, #2c82da 100%); filter: progid<ins>:</ins>dximagetransform.microsoft.gradient(startColorstr='#2c81da', endColorstr='#2c82da', GradientType=0); }
清单 13-8 显示了处于活动状态的主按钮的 CSS 规则。
清单 13-8。主按钮处于活动状态的 CSS 规则
.button.primary:active { background: green; }
清单 13-9 显示了用玻璃效果定义主按钮的规则。将的清单 13-9 与的清单 13-10 进行比较,它们用少得多的代码生成了非常相似的效果。同样,跳过 SVG 块,因为它是数据,而不是对人类有意义的东西。我们包括这些区块,因为我们不能让自己把一个不完整的清单放在书里。
清单 13-9。主按钮上的玻璃效果规则
*/** * * Primary button with Glass effect* * */* .button.primary.glass { background: #51a2ff; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzUxYTJmZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjQ4 JSIgc3RvcC1j b2xvcj0iIzM3OTVmNSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUxJSIgc3RvcC1jb2xvcj0iIzI1N2JkMSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMy NTZlYmEiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9 IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top, #51a2ff 0%, #3795f5 48%, #257bd1 51%, #256eba 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #51a2ff), color-stop(48%, #3795f5), color-stop(51%, #257bd1), color-stop(100%, #256eba)); background: -webkit-linear-gradient(top, #51a2ff 0%, #3795f5 48%, #257bd1 51%, #256eba 100%); background: -o-linear-gradient(top, #51a2ff 0%, #3795f5 48%, #257bd1 51%, #256eba 100%); background: -ms-linear-gradient(top, #51a2ff 0%, #3795f5 48%, #257bd1 51%, #256eba 100%); background: linear-gradient(top, #51a2ff 0%, #3795f5 48%, #257bd1 51%, #256eba 100%); filter: progid<ins>:</ins>dximagetransform.microsoft.gradient(startColorstr='#51a2ff', endColorstr='#256eba', GradientType=0); }
清单 13-10 显示了用阴影效果定义主按钮的规则。同样,清单 13-10 产生的效果类似于清单 13-9 产生的效果,但是使用的代码要少得多。
清单 13-10。主按钮上的阴影效果规则
*/** * * Primary Button with the Shadow Effect (approximates the Glass look)* * */* .button.shadow { background: #2c81da; -webkit-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0.4);
-moz-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0.4); box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0.4); }
清单 13-11 显示了为两个有开始处理的按钮创建开始按钮处理的第一个规则。正如我们之前提到的,我们在按钮的所有边上创建一个 5 像素的边框,使处理的宽度和高度比实际的按钮多 10 像素。
清单 13-11。定义一开始按钮处理的第一个规则
*/** * * Outset button styles* * */* .button.outset { content: ""; display: inline-block; position: relative; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; -webkit-box-shadow: inset 2px 2px 2px rgba(0, 0, 0, 0.3), inset 0 0 3px rgba(0, 0, 0, 0.4); -moz-box-shadow: inset 2px 2px 2px rgba(0, 0, 0, 0.3), inset 0 0 3px rgba(0, 0, 0, 0.4); box-shadow: inset 2px 2px 2px rgba(0, 0, 0, 0.3), inset 0 0 3px rgba(0, 0, 0, 0.4); }
清单 13-12 定义了开始按钮处理的悬停状态。
清单 13-12。开始按钮处理的悬停状态
.button.outset:hover { top: 0; left: 0; -webkit-box-shadow: inset 50% 50% 0 #ffffff; -moz-box-shadow: inset 50% 50% 0 #ffffff; box-shadow: inset 50% 50% 0 #ffffff; }
清单 13-13 使用:before
伪选择器语法在按钮前生成一个元素。然后,我们使用该元素来创建按钮周围的区域。此外,我们将生成的元素的z-index
属性设置为–1,以便访问者可以单击按钮。
清单 13-13。用:before 伪选择器生成开始元素
.button.outset:before { content: ""; width: 100%; height: 100%; display: block; z-index: -1;
position: absolute; padding: 5px; background: #CCC; left: -5px; top: -5px; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; -webkit-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4); -moz-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4); box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4); }
清单 13-14 显示了定义 Go 按钮的样式规则,它是圆形的,比其他按钮小得多。
清单 13-14。定义 Go 按钮外观的规则
*/** * * Go Button Styles* * */* .button.go { font-size: 11px; width: 22px; height: 17px; -webkit-border-radius: 100%; -moz-border-radius: 100%; border-radius: 100%; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; border: none; padding: 5px 0 0 0; text-align: center; }
清单 13-15 显示了定义开始 Go 按钮的规则。
清单 13-15。定义开始执行按钮
*/** * * Go Button with outset treatment* * */* .button.go.outset:before { content: ""; width: 100%; height: 100%;
display: block; z-index: -1; position: absolute; padding: 3px; background: #CCC; left: -3px; top: -3px; -webkit-border-radius: 100%; -moz-border-radius: 100%; border-radius: 100%; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; -webkit-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4); -moz-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4); box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4); }
清单 13-16 显示了定义脉动效果的规则。如果访问者的浏览器不支持动画,当他们的鼠标指针停留在按钮上时,仍然会得到一个效果。他们得到了清单 13-6 中定义的悬停样式。
清单 13-16。定义悸动效应
*/** * * Shadow Throb* * */* @-webkit-keyframes shadowThrob { 0% { -webkit-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0); -moz-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0); box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0); } 50% { -webkit-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0.2); -moz-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0.2); box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0.2); } 100% { -webkit-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0); -moz-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0); box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 12px 2px rgba(255, 255, 255, 0); }
} @-moz-keyframes shadowThrob { 0% { opacity: 0.0; } 50% { opacity: 0.5; } 100% { opacity: 1.0; } } @-o-keyframes shadowThrob { 0% { opacity: 0.0; } 50% { opacity: 0.5; } 100% { opacity: 1.0; } } @keyframes shadowThrob { 0% { opacity: 0.0; } 50% { opacity: 0.5; } 100% { opacity: 1.0; } }
清单 13-17 显示了用图标按钮处理定义主图标的第一个规则。正如我们在本章前面提到的,实际的图标是一个 SVG 图像,它让我们可以避免 HTTP 请求并利用浏览器的缓存。对于 Internet Explorer 8 和更早的版本,我们可以添加一个图像作为后备。在这种情况下,为了压缩已经很大的上市规模,我们没有这样做。正如我们之前提到的,您应该跳过 SVG 图像数据。
清单 13-17。为有图标的按钮定义图标
*/** * * Button Icons* * */* .buttonIcon { position: absolute; left: 2px; top: 2px; background-repeat: no-repeat;
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHEAAAAZCAYAAAAG2cHnAAAAGX RFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA+dpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldC BiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bn M6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMC AgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbn MjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLj AvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYW RvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc2 91cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wOkNyZWF0ZURhdG U9IjIwMTItMDYtMThUMjM6MjY6NDktMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDEyLTA2LTIxVDAyOjM1OjU2LTE5OjAwIi B4bXA6TWV0YWRhdGFEYXRlPSIyMDEyLTA2LTIxVDAyOjM1OjU2LTE5OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgeG1wTU 06SW5zdGFuY2VJRD0ieG1wLmlpZDo5MzdGQ0M0OUIxRUMxMUUxOEJFODlBMDkxM0UyQ0FDNCIgeG1wTU06RG9jdW1lbnRJRD 0ieG1wLmRpZDo5MzdGQ0M0QUIxRUMxMUUxOEJFODlBMDkxM0UyQ0FDNCI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbn N0YW5jZUlEPSJ4bXAuaWlkOjkzN0ZDQzQ3QjFFQzExRTE4QkU4OUEwOTEzRTJDQUM0IiBzdFJlZjpkb2N1bWVudElEPSJ4bX AuZGlkOjkzN0ZDQzQ4QjFFQzExRTE4QkU4OUEwOTEzRTJDQUM0Ii8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+ID wveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+7zXkvQAABN9JREFUeNrsWmFkHEEUvjR1LOE0dZTTEPKrHCGEkCpHCO EIRwghHCEkQighhJIKR/vnqpUqRwmpVAipVEiFUlKlXJUQQivVSDTyKzRa2zd8y8uY2d3ZnU1S8vjs3uzM25337Xvz5u01uK 6bupL/W66F6NNOqBC2CN8ILo5baG+/MmMi0mKDxB7CMqFGOCSME+4SGnAcR3sN/XoiPKh7AUhKsoSnFvXlw1uRwqkCFcIOoV 9qv0Mo4cjb+9G/otGngyczhuNMMcPulYT+IuGA8Mmizr6wfVWNVcIaoQm/hwnrhBP3rJygfQj9mjCuakjiAxynEzLwtHQf2/ qFff5A9+JlIHGOsEpoJNzAQ3H5SljCkcsi+jdi/JwBiSlm4CnLBp5iBKYSIHFEssNsDF1pwiCcYgTOIF6QMqEzLIlFhMQM4T ahjgc7hiJHGuzgZsfoV8e4jCYU+5HIQ96UZQJnNPez4SmyTETUlQdhjsYTO2FrJ4hEEQp72bmQ94RcwAPk0M/FONHWg1BrQi IncjKmgSc1a60tEtvYy8slynN3EwohwqkD/Wkdie2EzzgfwwPtwqu8Pq1IXJZxbGXXMujvYnwKi3yHIYk2iJz0SZZskJiGrV TyyFBXhuUUKcnWwywv4USWdSTOsTC2iQca0GR3riarHEDbJgtncxFI5MmIaXiaCEiSXIuJkkrqhrrKkmflmP1FbvFbMRexTGV VJG4h7t6Egl9IUrxthZ94241GjHOhpyNEyu2GMNaEJQJtkNgGw6pkg9AVgUT+e4PpW2XnRckbh1Qk/iTcwlrmSutZNYBEvqV YR5vQ0wxSUzGMOi2FaB3GQm5TwpJYVK09WEpUEiUrbZTIkJ1lVTrnY8+QeB17/mbCEaoOKVRiPGkLqBfw64esenEEvXHkIXR UCX8JzxR9RnH9MfrHkTbo6kVF6gm71kHoV4yR+wWVMDtxnkZVxsFvcXzO+n4h/MC5sOUIu9bNxl16T+yAjh30HZWuj6JdXN9 DuItyPwd7SR4q96SUfk0x97jbIe5RzdL9uSfW/DzxMq+J3Ujj12BMj7AKdFcYsQ767QZsiVxNKr+rmdsk26fJ8tRCkjQo/b6 vIPEADub1aYFtEslOSxaz0wLKeivS2jSISXmTG5RS/xVkiFmD+3VqvMx7mZugl8uWZs00RUERPfrxPML75hUv5QhzsDMkcq8 J2ieuavaJO1I2GXWfWERYWdIYKo2ihO7aEoycMfT8kmYD/4LVRj1iWy1WfsZkUnxqp8Ke9/wqNhuschC1YrPO3rCNCBWbARC 4oJlY2KxvAfd3DNdgEao+BCwfRcv11wzCdjaAxAJeNN+yW8mndjqsePPTaNfVTkuGJHpfA2oxCORE1hThOMwWw5GSCl0SZxt DCKVpicQcvDVv6ytGHW953fJXDC+Ez1s2zDye13SfKObxUprjjqIMlkrAK7tAYBWOULDxPbGM0HQqTeoU7eWY3xOnWNaZhGE qLJN0Db2ZJzR9CRNo9aOwN/FtRfzPIyPMK5KR7Rhf9mcTNsgsooNp2c3BGrl4zgQakdjg82+3XlRDcoRXhHeE76jKZPFHngK hRNhHNeVthP/YnLc0GPbPoDqyf87P2Ud4E2pCIf6yKMpNAyCsBQQeglBB7GvCx6s/p12chCHxSi65/BNgAM6z2oOaR+9QAAA AAElFTkSuQmCC); }
清单 13-18 显示了为图标图像腾出空间和定位的两个相关类。
清单 13-18。为图标腾出空间并定位
*/** * * Checkmark Icon Button* * */* .icon_checkmark { background-position: -64px -2px; width: 20px; height: 21px; } .btn_checkmark { padding-left: 27px; }
当你通读一本书时,这个 CSS 看起来很多,但在大的计划中它并不真的那么多。我们已经提到了一个可悲的事实,我们已经看到了大于一兆字节的 CSS 文件。想象一下试图理解那些怪物!不过,我们希望我们的 CSS 对你有意义。此外,我们鼓励您访问我们的按钮控件示例站点,并研究实际的 CSS。我们的按钮控件的示例站点是[
clikz.us/BookExamples/buttonControl.php](http://clikz.us/BookExamples/buttonControl.php)
总结
在这一章中,我们为一个元素创建了另一个控件,这个控件似乎不值得我们去做。虽然我们理解第一眼的评价,但我们认为稍加思考就会明白为什么为按钮创建一个控件是有意义的。最大的原因是按钮到处都是。大多数网站,尤其是电子商务网站,几乎每个页面上都有多个按钮。几乎总有一种方法可以购买某些东西,定制某些东西,与支持人员或销售代理进行聊天,等等。考虑到按钮的数量,我们肯定希望它们的外观和行为都一致。按钮控件是让他们合作的好方法。
第二(但仍然是一个很大的好处),我们创造了一些东西,让我们的队友不必考虑如何制作按钮。如果他们使用我们的控制,他们会得到一个按钮,保证符合公司的外观和行为标准。因此,他们的发展速度增加;简而言之,他们用更少的时间完成更多的工作。我们怎么能不喜欢呢?
第三(但仍然很重要),维护更容易。假设出于商业考虑(比如公司范围内的品牌重塑工作),必须改变所有的按钮。我们改变一个代码块,控件,网站上所有的按钮都会改变。
此外,我们不得不提到封装的概念。按钮有问题吗?只需看看按钮控件。当您的问题被隔离在一个控件中时,故障诊断会快很多。
综上所述,这些优势以很低的成本提供了强大的功能和灵活性。这就是为什么我们喜欢我们的按钮控件,尽管乍一看它似乎有点过头了。
十五、价格控制
价格控制实际上是两个控制,布尔。除了实际的价格控制,我们还有运输控制。Tt:我们把它们放在同一个章节里,因为它们经常放在一起。价格控制包含价格信息,不仅仅是最终价格,还有底价、折扣、税等等。装运控制包含装运信息。
在开始之前,我们想展示一个完成的控件的图像。这是一个单一的图像,因为,再一次,控件经常在一起。
图 14-1。价格和运输管制
如果您想查看价格和运输控制的运行情况,请在我们为支持本书而创建的示例网站上查找它们。对于样品价格控制,地址为[
clikz.us/BookExamples/priceControl.php](http://clikz.us/BookExamples/priceControl.php)
价格控制
和我们所有的其他控件一样,这个控件实际上是一个函数和一组应用于函数输出的 CSS 规则。然而,由于没有看到 HTML 很难理解规则,我们从实际上是控件的函数生成的 HTML 开始。
我们看到许多 web 开发人员一直在努力解决的一个问题是,在左对齐的元素(如产品名称)和右对齐的内容(如价格)之间创建一个点前导符。为了帮助人们克服这个问题,价格控件包括一个点前导符,它总是弯曲以填充左对齐元素和右对齐元素之间的空间。
HTML
价格控制的 HTML 并不复杂。它只是一系列的div
元素和span
元素,一个sup
元素保存货币,另一个sup
元素保存价格的小数部分。
注意保存价格信息的 HTML 包含一个运输信息的占位符。
清单 14-1 显示了 HTML 的一部分,它保存了图 14-1 中显示的价格清单的价格信息。
清单 14-1。价格控件插入的 HTML 的价格部分
`
$1599.99
CSS
价格控制的 CSS 只包含一些规则。我们分别描述每一个,这样我们就可以讨论每一个是如何工作的,以及这些规则是如何结合在一起的。
在这个过程中,我们还描述了虚线领导是如何工作的。为了获得跨各种背景(包括渐变)工作的虚线前导,我们使用了一个不太为人所知的效果,它是overflow:hidden
属性的一部分。迈克尔从妮可·沙利文的博客中了解到这一点。如果你不熟悉妮可·沙利文的 CSS 工作,花点时间看看她的想法;这是值得花的时间。这里是 W3C CSS 2.1 规范中overflow:hidden
属性的棘手部分:
表格的边框、块级替换元素或建立新的块格式化上下文的正常流程中的元素(如具有“溢出”而非“可见”的元素)不得与元素本身在同一块格式化上下文中的任何浮动重叠。
级联样式表 2 级修订版 1 (CSS 2.1)规范
这可以分解为一个浮点数旁边的overflow:hidden
元素占用了剩余的空间。这就是我们如何让点的可变宽度自动占据标签和数量之间的剩余空间。我们正在创建一个嵌套的overflow:hidden
排列,其中我们使用priceSubWrap
来包含数量,使用div
来包含点分隔符。图 14-2 标注了价格控制的各个部分。
图 14-2。价格管制的部分
我们使用另一个 base64 编码的图像作为点。但是,由于 base64 图像不能在 Internet Explorer 6 或 7 中运行,我们还为使用这些浏览器的访问者提供了一个指向该图像的链接。我们对运输栈中的日历图标使用相同的技术。
现在让我们开始看看 CSS 规则。清单 14-2 显示了应用于价格堆栈的第一条规则。它为保存描述和价格的行设置位置和宽度。
清单 14-2。设定价格的位置和宽度
.dottedPriceWrap { position: relative; width: 200px; }
清单 14-3 显示了priceLabel
规则,它对价格线的描述部分进行了样式化。特别是,它将float
属性设置为left
(这使得类为priceSubWrap
的div
元素出现在价格标签旁边)并设置填充。
清单 14-3。对价格的描述
.priceLabel { float: left; padding-right: 4px; }
清单 14-4 指定了实际的定界符(或前导符)。在这里,我们将overflow
属性的值设置为hidden
。text-indent
属性确保了类别为dottedSpanner
的div
中的文本不会出现。我们必须在那个div
元素中放些东西,否则它会崩溃,而不会显示我们的分隔符,但是我们不希望那个文本出现。正如我们在本章前面所讨论的,base64 编码的数据包含构成我们的点分隔符的点的图像。
提示这种技术提供了一种适用于各种背景(包括渐变)的点状前导。
清单 14-4。指定分隔符(圆点)
`.dottedSpanner {
overflow: hidden;
line-height: inherit;
text-indent: -999em;
background: url(data:image/gif;base64,R0lGODlhBwAFAJECAGZmZGJmZf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1w
bWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3
LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5
OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6
Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4
bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JU
b29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NUZCQUFFNUJBRjU3
MTFFMUE4NkZBMzc4MEIwMDVGMEQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NUZCQUFFNUNBRjU3MTFFMUE4NkZBMzc4
MEIwMDVGMEQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1RkJBQUU1OUFGNTcxMUUx
QTg2RkEzNzgwQjAwNUYwRCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1RkJBQUU1QUFGNTcxMUUxQTg2RkEzNzgwQjAw
NUYwRCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH/
/v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3
trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBv
bm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgn
JiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAAHAAUAAAIHjI6Ay+1XAAA7)
repeat-x bottom left;
}
background: url(data:image/gif;base64,R0lGODlhBwAFAJECAGZmZGJmZf///wAAACH/C1hNUCBEYXRhWE1QPD94
cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJh
ZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3
OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5
bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20v
eGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0
cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90
b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NUZCQUFFNUJBRjU3MTFFMUE4NkZBMzc4MEIw
MDVGMEQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NUZCQUFFNUNBRjU3MTFFMUE4NkZBMzc4MEIwMDVGMEQiPiA8eG1w
TU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1RkJBQUU1OUFGNTcxMUUxQTg2RkEzNzgwQjAwNUYw
RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1RkJBQUU1QUFGNTcxMUUxQTg2RkEzNzgwQjAwNUYwRCIvPiA8L3JkZjpE
ZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw
7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmo
p6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFg
X15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkY
FxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAAHAAUAAAIHjI6Ay+1XAAA7) repeat-x bottom left;
}`
清单 14-5 显示了为使用 IE6 或 ie7 的访问者指定在哪里找到点的图像的规则。它还指定图像应该在整行中水平重复。
清单 14-5。为 IE6 和 IE7 指定点
.ie6 .dottedSpanner, .ie7 .dottedSpanner { background: url(/img/period.gif) repeat-x bottom left; }
清单 14-6 显示了对价格线的金额部分进行样式化的规则。特别是,它指定价格应该向右浮动,并且有 2 个像素的左填充(以便在分隔符和价格值之间留出一点空间)。
清单 14-6。造型价格值
.priceAmt { float: right; padding-left: 2px; }
清单 14-7 中的显示了priceSubWrap
类。这个类应用于保存价格金额和点分隔符的div
。为了让分隔符图像填充价格标签和价格值之间的空间,该类将overflow
属性的值设置为hidden
。它还指定了div
元素的position
属性的值为relative
。
清单 14-7。围绕价格和分隔符设计包装器
.priceSubWrap { overflow: hidden; position: relative; }
清单 14-8 设计货币符号($,,€等)的样式。)使用一个简单的规则来指定符号的大小和位置。我们也对小数点右边的价格部分使用这种样式。
清单 14-8。设计货币符号
sup.currency { font-size: 70%; top: auto; position: relative; vertical-align: text-top; line-height: 125%; }
控制
price 函数构建一个字符串,其中包含 HTML 元素和参数中指定的值,除了参数$echo
。$echo
参数指示是将 HTML 字符串插入调用该函数的页面,还是将 HTML 作为字符串返回给其他函数。
注意通常,输入这些控件(以及大多数其他控件)的值来自数据库。为了简单起见,我们在示例站点上没有使用数据库。相反,我们只是手工插入值,因为我们的重点是 HTML 和 CSS,而不是后端工作。
表 14-1 描述了price
功能的参数。
清单 14-9 显示了定义价格函数的 PHP 代码。
清单 14-9。价格功能
`<?php
function price($label, $currencySymbol, $wholeNumber, \(remainder, \)echo) {
\(output = '<div class="dottedPriceWrap">';
\)output .= '
$output .= \(label; \)output .= '
\(output .= ' <div class="priceSubWrap">'; \)output .= '
$output .= ' '. \(currencySymbol . '</ sup><span>'.\)wholeNumber.'.'.\(remainder.'</sup>'; \)output .= '
\(output .= ' </div>'; \)output .= '
$output .= '';
if($echo == "return"){
return $output;
} else {
echo $output;
}
}
?>`
装运控制
正如我们在本章开始时提到的,装运控制是一个独立的控制。但是,因为装运控制通常跟在价格控制之后,所以我们在这里解释装运控制。从 shipping control 函数生成的 HTML 开始,将提供上下文,帮助您理解 CSS 规则和实际控件的功能。
HTML
装运控件的 HTML 包含一个具有三个跨度的单个div
。跨度让我们可以创建单独的规则来设置运输图标、运输标签和运输日期的样式。清单 14-10 显示了保存运输信息的 HTML。
清单 14-10。价格控件插入的 HTML 的运输部分
<div class="shippingWrap"> <span class="iconShipping"></span> <span class="shipLabel">Estimated Ship Date: </span> <span class="shipDate">July 25, 2013</span> </div>
到目前为止,你可能已经习惯了我们的 HTML 风格。我们使用具有有意义的类名的简单元素,然后通过 CSS(以及 JavaScript,当我们无法从 CSS 中获得我们想要的东西时)指定如何处理这些元素。因此,让我们研究一下将这些内容转换成价格控件所需输出的 CSS。
CSS
由于装运栈由一个div
和三个跨度组成,我们只需要四个规则来设计装运控制。
清单 14-11 显示了shippingWrap
规则,它指定了装运栈的位置和字体大小。它提供了 22 个像素的左填充值来为日历图标腾出空间,并提供了 5 个像素的上边距值来在价格和运输信息之间留出一点空间。
清单 14-11。样式化出货信息
.shippingWrap { position: relative; padding-left: 22px; font-size: 12px; margin-top: 5px; }
清单 14-12 显示了iconShipping
规则,它定义了我们放在运输信息左边的日历图标。我们将一个 18 像素的图像放入一个 22 像素空间的左侧(这 22 个像素是由shippingWrap
规则定义的;参见清单 14-11 ,它在日历图标和发货信息之间提供了一个 4 像素的空间。与分隔符中的点一样,我们使用 base64 编码的数据块来指定图标图像。
清单 14-12。插入日历图标
.iconShipping { width: 18px; height: 18px; position: absolute; left: 0; top: 1px; background-repeat: no-repeat; background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAMAAABhEH5lAAAAGX RFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAABVhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldC BiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bn M6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMC1jMDYwIDYxLjEzNDc3NywgMjAxMC8wMi8xMi0xNzozMjowMC AgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbn MjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLj AvIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYW RvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc2 91cmNlRXZlbnQjIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIy IgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IE1hY2ludG9zaCIgeG1wOkNyZWF0ZURhdGU9IjIwMTItMD YtMThUMjM6MjY6NDktMDU6MDAiIHhtcDpNb2RpZnlEYXRlPSIyMDEyLTA2LTIyVDE3OjQyOjUxLTA1OjAwIiB4bXA6TWV0YW RhdGFEYXRlPSIyMDEyLTA2LTIyVDE3OjQyOjUxLTA1OjAwIiBkYzpmb3JtYXQ9ImltYWdlL3BuZyIgeG1wTU06SW5zdGFuY2 VJRD0ieG1wLmlpZDpGRTVEQjQyRkI0QjUxMUUxOEJFODlBMDkxM0UyQ0FDNCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZD pGRTVEQjQzMEI0QjUxMUUxOEJFODlBMDkxM0UyQ0FDNCIgeG1wTU06T3JpZ2luYWxEb2N1bWVudElEPSJ4bXAuZGlkOjAxOD AxMTc0MDcyMDY4MTE4QTZEQjZBNUFBMERGQUNCIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0Om FjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDowMTgwMTE3NDA3MjA2ODExOEE2REI2QTVBQTBERk FDQiIgc3RFdnQ6d2hlbj0iMjAxMi0wNi0xOFQyMzoyNjo0OS0wNTowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUG hvdG9zaG9wIENTNSBNYWNpbnRvc2giLz4gPC9yZGY6U2VxPiA8L3htcE1NOkhpc3Rvcnk+IDx4bXBNTTpEZXJpdmVkRnJvbS BzdFJlZjppbnN0YW5jZUlEPSJ4bXAuaWlkOjAxODAxMTc0MDcyMDY4MTE4QTZEQjZBNUFBMERGQUNCIiBzdFJlZjpkb2N1bW VudElEPSJ4bXAuZGlkOjAxODAxMTc0MDcyMDY4MTE4QTZEQjZBNUFBMERGQUNCIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3 JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZW5kPSJyIj8+Ih6y4AAAABtQTFRF5eXljcP38fHx////1dXVJYnnLZ T0Np7/AAAAGCCkcgAAAAl0Uk5T//////////8AU094EgAAAD1JREFUeNpi4OBgYkACTBwcDBwMjOxIgBEowMTIhgIYgZrYWF EAG1A3MxoACbGwoCAcQkNZI0oAggMRIMAA9O0D+65FZsYAAAAASUVORK5CYII=); }
清单 14-13 为使用 IE6 或 IE7 的访问者指定了日历图标的位置。
清单 14-13。为 IE 6 和 IE7 插入日历图标
.ie6 .iconShipping, .ie7 .iconShipping { background-image: url(/img/icon_calendar.png); }
清单 14-14 显示了shipLabel
规则,它将短语“预计发货日期”设计成灰色阴影,并且是一个块。由于shipLabel
规则位于span
元素上,我们必须将它显示为一个块,以保持它在自己的行上。
清单 14-14。对装运块的标签部分进行造型
.shipLabel { color: #777;
display: block; }
现在让我们转到控件的 PHP 部分。
控制
.shipDate
函数做的事情与price
函数非常相似。也就是说,它构建一个包含 HTML 元素的字符串,如果$echo
参数包含"echo"
,则将内容发送给浏览器,或者如果$echo
参数包含"return"
,则将内容发送给调用该函数的函数。但是,它使用不同的参数并创建两行而不是一行。
注意为了描述函数,我们将它们分成两个清单。在我们的示例站点上,
clikz.com/BookExamples/priceControl.php
,这两个函数在同一个代码块中。
表 14-2 描述了 shipDate 函数的参数。
正如shipDate
参数的描述所示,该参数只是一个字符串。如果你想确保你总是得到相同的日期格式,把它分成三个参数:年,月,日。如果想要更灵活的控制,甚至可以添加第四个参数来指定日期的格式。因为我们假设我们所有的(虚构的)客户都在美国,所以我们将使用典型的美国日期格式。
清单 14-15 显示了定义shipDate
函数的 PHP 代码。
清单 14-15。发货日期功能
<?php function shipDate($shipDateLabel, $shipDate, $echo){ $output = '<div class="shippingWrap">'; $output .= ' <span class="iconShipping"></span>'; $output .= ' <span class="shipLabel">'; $output .= $shipDateLabel; $output .= '</span>'; $output .= ' <span class="shipDate">'; $output .= $shipDate; $output .= '</span>';
` $output .= '';
if($echo == "return"){
return $output;
}else {
echo $output;
}
}
?>`
使用控件
要使用这些控件,您必须将php
标签插入您的网页。每个价格和运输控制都需要一个单独的php
标签。考虑一下我们在本章中使用的例子。图 14-3 显示了我们的示例价格堆栈(由三个价格控件和一个运输控件组成)。它与图 14-1 的图像完全相同,为了方便起见,在此重复一遍。
图 14-3。一个简单的价格栈(再次)
为了创建我们的示例价格栈,我们需要四个php
标记,每个标记调用价格控件或运输控件。清单 14-16 显示了创建价格栈的函数调用,如图图 14-3 所示。
清单 14-16。使用价格和运输控制
`
`总结
价格控件实际上是两个控件,一个用于在价格堆栈中生成单独的行,另一个用于创建运输信息。这种关注点的分离提供了许多好处。首先,您可以堆叠任意数量的价格堆叠行(也许是为了显示各种折扣或费用)。另一方面,您可以将运输信息放在价格栈的顶部或底部(甚至在价格线之间,如果您能想到一个好的理由这样做)。您也可以省去一个控件或另一个控件,让定价单独存在,或者让装运块单独存在。
我们将在下一章中同时使用价格控制和运输控制,其中我们描述了产品控制(或者我们通常称之为产品堆栈)。正如我们不断重复的那样,我们的大多数控件都由其他控件组成,尽管通常带有附加信息。记住分形的比喻:如果你放大一个分形图像,你会看到一个非常相似的图像。类似地,如果你查看我们的一个控件,你可能会发现更多的控件。
十五、产品控制
在前面的章节中,我们已经提到了产品控件,它的大部分内容都使用其他控件。因为它是其他控件的堆叠,并且它通常的布局是垂直的,一项信息堆叠在另一项上,所以我们通常称它为产品堆叠。该控件还可以具有更水平的布局,其中内容出现在两个不同大小的列中。正如我们对其他对照品所做的那样,我们将产品的变异称为对照品处理。它有两种处理方式:垂直和水平。
在我们开始这个控件之前,让我们花一分钟回忆一下我们制作所有这些控件的原因:提高性能。最大的性能收益无疑属于开发人员。他们不用创建自己的产品列表,他们可以使用我们的控件,只需花很少的时间就可以获得产品列表。此外,因为控件将它们的代码封装在一个容易找到的地方,所以调试更加容易和快速,这是开发人员的另一个性能改进。游客也可以看到性能的提升。当每种产品看起来都有点不同时,区分这些差异就成了一个挑战。但是,由于从一个产品列表到下一个产品列表有一致性的保证,区别很容易发现,访问者可以很快找到他们想要的产品。最后,如果访问者更容易找到他们想要的,他们不仅会买更多,而且会告诉他们的朋友他们找到了一个多么好的网站。这对企业来说是一大胜利。
产品控件使用链接控件(提供帮助链接)、价格控件和按钮控件。它还添加了产品的图像和一些可能会吸引客户购买产品的文本。(在我们的例子中,我们使用了无意义的文本,但我们并不是真的想出售任何东西。)
如果您想在网页上看到产品堆栈,您可以在我们的示例站点找到:[
clikz.us/BookExamples/productControl.php](http://clikz.us/BookExamples/productControl.php)
图 15-1 显示了使用中的产品控制。在垂直方向上,它占据了整个页面(这是在我们将它缩小一点之后,这样我们就可以在带有图像的页面上看到标题)。因为产品控件提供了大量的信息,所以占用了大量的空间。
图 15-1。产品控制
产品控件的水平布局占用相同的空间,但是对于书籍布局来说更友好一些。我们发现垂直布局在网页上效果最好,我们可以将许多产品控件放在一起,帮助客户比较产品。也就是说,水平布局也有其 web 用途,尤其是当它是页面上唯一的产品控件时。
图 15-2 显示了产品控制的水平布局。
图 15-2。产品控制的横向布局
产品控件中的大部分内容,无论是垂直的还是水平的,看起来都很熟悉。帮助链接(圆圈中的问号)与第十一章中的帮助链接相同。装运区(日历图标、“预计装运日期”文本和实际日期)与在第十三章中看到的装运区相同。这三行价格信息也与在第十三章中看到的相同。“给我买”按钮是第十二章中描述的按钮控件的一个实例。
现在您已经看到了产品控件的样子,是时候学习如何让它工作了。让我们从如何在网页中放置一个开始。
插入控件
为了指定产品控件的水平或垂直处理,我们从页面中调用一个 PHP 函数,并指定两个参数:一个数据源和一个处理。我们稍后将讨论数据源。现在,就把它想象成我们在运行时在控件中处理的一个数据块。
垂直处理的名称为pcTreatment1
(用于产品控制处理 1)。这不是最原始或最具描述性的名字,但它遵循了我们通常的命名惯例。横向处理的名字(你大概已经猜到了)是pcTreatment2
。
对于我们的示例页面,我们首先创建垂直布局,然后创建水平布局,两者之间有几个空行以提供一些空白空间。清单 15-1 显示了在网页中创建产品控件的两种不同处理的代码。
清单 15-1。将产品控件放到页面上
`
pcTreatment1
pcTreatment2
`现在你已经知道了输入是什么样子,让我们考虑一下输出。
由控件产生的 HTML
控件创建在页面中使用的 HTML。我们提供 HTML 以便您可以看到控件在运行时产生的内容。HTML 嵌入在控件中,我们将在后面展示,但是它本身和所有的值都在适当的位置更容易阅读。
当你通读清单 15-2 时,注意类名。制作这种控件的诀窍在于类名。HTML 元素实际上只包含数据。一个有经验的 CSS 用户知道,给定不同的 CSS 规则,你可以从相同的元素制作完全不同的布局。这就是我们如何为我们的控制实现不同的处理。我们依靠类名来给我们控制任何给定处理的布局的入口点。因此,整个展示依赖于类名。
注意 清单 15-2 显示了只是垂直处理的 HTML 结果。清单已经很大了,所以我们没有通过添加横向处理使它更大。此外,HTML 中处理的唯一区别是
pcTreatment1
和pcTreatment2
。这两种处理之间的所有其他差异都在 CSS 规则中。
清单 15-2 显示了垂直处理的 HTML,所有的值都在适当的位置。我们添加了一些注释来指出产品控件中的其他控件。为了便于查找,我们用粗体显示这些注释。
清单 15-2。垂直处理的产品控件创建的 HTML】
<article class="productControlWrap pcTreatment1"> <img src="/img/laptop1.jpg" alt="This is a picture" class="pcImage"><h2 class="pcTitle">The Worlds Best Laptop</h2> ***<!-- Here's the top of a price stack -->*** <div class="pcPriceStack"> <div class="dottedPriceWrap"> <div class="priceLabel"> Retail Price </div> <div class="priceSubWrap"> <div class="priceAmt"> <sup class="currency">$</sup><span class="wholeNumber">1599.</span><sup class="currency">99</sup> </div> <div class="dottedSpanner"> . </div> </div> </div> <div class="dottedPriceWrap"> <div class="priceLabel"> Discount
</div> <div class="priceSubWrap"> <div class="priceAmt"> <sup class="currency">$</sup><span class="wholeNumber">100.</span><sup class="currency">00</sup> </div> <div class="dottedSpanner"> . </div> </div> </div> <div class="dottedPriceWrap"> <div class="priceLabel"> Your Price </div> <div class="priceSubWrap"> <div class="priceAmt"> <sup class="currency">$</sup><span class="wholeNumber">1499.</span><sup class="currency">99</sup> </div> <div class="dottedSpanner"> . </div> </div> </div> </div> ***<!-- End of price stack control -->*** *** <!-- Here's the shipping stack,*** *** which is not within the price stack -->*** *** <!-- Remember, the price control can create produce price lines*** *** and shipping blocks independently of one another. -->*** <div class="pcShippingStack"> <div class="shippingWrap"> *** <!-- Note the anchor element; that is the help link control -->*** <span class="iconShipping"></span><span class="shipLabel"><a href="javascript:;" data- link-type="help" class="link">Estimated Ship Date<span class="linkIcon" data-tooltip- message="You will receive a definitive date during checkout"></span></a> </span><span class="shipDate">July 25, 2013 </span> </div> </div> ***<!-- End of shipping stack -->*** <div class="pcDescWrap"> <div class="pcDescText"> Sartorial williamsburg small batch helvetica mixtape wayfarers. Art party biodiesel before they sold out authentic. </div> <ul class="pcDescBullets"> <li class="pcDescLI"> Squid pinterest carles, fingerstache forage scenester. </li> <li class="pcDescLI"> Pinterest carles, fingerstache forage scenester. </li>
`
Carles, fingerstache forage scenester.
<!-- Here's a button control, constituted as a link, since it's not in a form -->
Buy Me `
如果我们不在控件中使用控件,这个列表会更大。如果没有嵌套控件,我们就必须包含每个包含控件的所有元素。压缩代码的能力很有帮助,既能保持每个页面的代码更小,又能帮助我们阅读现有的代码。我们可以在清单 15-2 中查看锚元素的属性,立即知道一个是弹出帮助链接,另一个是购买按钮。代码type="help"
给出了一个相当大的线索,后面跟着class="button primary shadow"
和Buy Me
。与任何语言中的大多数代码一样,学习发现代码中的东西只是一个练习的问题。如果你像我们一样整天和控制打交道,你也会在一瞬间发现它们。
CSS
与所有其他控件一样,CSS 以一种吸引人的方式安排产品控件(我们希望如此)。在本例中,我们有两个处理,我们需要为它们安排相同的内容(不包括类名)。由于一些规则适用于两种处理方式,所以我们在 CSS 中有三个逻辑部分:适用于两种处理方式的规则,仅适用于垂直处理方式的规则(pcTreatment1
),以及仅适用于水平处理方式的规则(pcTreatment2
)。我们将为每个规则组提供自己的列表。
清单 15-3 显示了适用于产品控件的垂直和水平处理的 CSS 规则。
清单 15-3。产品控制两种处理的 CSS 规则
*/** * * Starting with the base definition that are shared* * * across all of the productControls. Besides the treatment* * * for the image, we're basically reseting margins and setting* * * initial font-sizes for consistency. We want to keep the styling* * * light so we reduce the number of times we have to override* * * the styles in the treatment specific sections.* * */* .productControlWrap { position: relative; font-size: 14px; } .pcImage { -webkit-box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); -moz-box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); box-shadow: 2px 2px 3px 0 rgba(0, 0, 0, 0.3); border: 1px solid #999; border: 1px solid rgba(0, 0, 0, 0.3); } .pcTitle {
font-size: 18px; margin: 0; line-height: 110%; color: #1f81dd; } .priceLabel { color: #555; } .priceAmt { color: green; font-weight: bold; } .pcDescBullets { margin: 0; padding-left: 20px; }
清单 15-4 展示了只适用于产品控件的垂直处理的 CSS 规则。如果您记得垂直处理的名称是pcTreatment1
,这个清单会更有意义。
清单 15-4。 CSS 规则仅针对产品控件的纵向处理
*/** * * Styles to affect the pcTreatment1 look.* * ** * */* .pcTreatment1 { width: 200px; color: #333333; } .pcTreatment1 .pcImage { width: 200px; height: 200px; margin-bottom: 10px; } .pcTreatment1 .pcTitle { margin-bottom: 10px; } .pcTreatment1 .shippingWrap { margin-bottom: 10px; } .pcTreatment1 .pcDescText { margin-bottom: 10px; line-height: 150%; } .pcTreatment1 .pcDescLI { margin-bottom: 10px; }
清单 15-5 显示了仅适用于产品控件水平处理的 CSS 规则。如果您记得水平处理的名称是pcTreatment2
,那么这个列表就更有意义了。
清单 15-5。 CSS 规则仅针对产品控制的横向处理
*/** * * Styles to affect the pcTreatment2 look.* * ** * */* .pcTreatment2 { width: 300px; padding-left: 230px; color: #333333; } .pcTreatment2 .pcImage { width: 200px; height: 200px; position: absolute; left: 0; top: 0; -webkit-box-shadow: 0 4px 4px 0px rgba(0, 0, 0, 0.4); -moz-box-shadow: 0 4px 4px 0px rgba(0, 0, 0, 0.4); box-shadow: 0 4px 4px 0px rgba(0, 0, 0, 0.4); } .pcTreatment2 .pcTitle { font-size: 24px; margin-bottom: 20px; } .pcTreatment2 .pcPriceStack { width: 200px; margin-bottom: 10px; } .pcTreatment2 .shippingWrap { position: absolute; left: 0; top: 220px; } .pcTreatment2 .shipLabel .link { color: #333333; } .pcTreatment2 .shipLabel .link:visited { color: #333333; } .pcTreatment2 .pcDescText { margin-bottom: 10px; line-height: 150%; } .pcTreatment2 .pcDescLI { margin-bottom: 10px; } .pcTreatment2 .button { position: absolute; left: 0; top: 270px;
}
因为这些规则并不复杂,所以没有必要像我们在其他章节中所做的那样分别描述每一个。这实际上是从其他控件构建控件的另一个好处:复杂性可以隐藏。我们将 CSS 规则用于链接、价格和按钮控件,但我们不必关心这些规则,因为它们是这些控件的一部分,我们在这里只是使用(而不是定义)它们。很高兴知道一旦一个控件工作了,我们就可以用同样的方式来使用它,就像某人将控件链接在一起制作一个页面一样。这就是封装的力量。多棒的东西!
产品控制的数据
产品控件需要足够的信息,试图通过函数参数提供字符串将会非常困难,并且很可能导致错误。相反,我们传入一个数据对象。在我们的例子中,我们选择使用一个 JSON (JavaScript Object Notation)对象,因为 JSON 是一个已经建立的标准,我们非常熟悉并且使用很多。
如果您不熟悉 JSON,花几分钟学习一下基础知识。JSON 在网络上的家是[
www.json.org](http://www.json.org)
(然而,对于快速浏览来说,它有点密集)。你可能会发现 W3 Schools ( [
www.w3schools.com/json/default.php](http://www.w3schools.com/json/default.php)
)的 JSON 教程更容易快速阅读。
检查我们的数据源,您会看到我们创建了一堆名称-值对。每一对都用逗号隔开。在每个名称-值对中,名称与值之间用冒号隔开。每个值,无论是名称还是值,都是一个字符串,因此用引号括起来。
仔细观察,您还应该看到一些不同的分组机制。例如,priceStack
名称的值是一组数据。在该集合中,还有一些用大括号括起来的集合。这是一种编码方式,即价格堆栈控件的数据由值的集合组成,每个值由定义价格堆栈控件中的价格线的数据组成。一旦你学会了阅读 JSON 的语法,它就非常方便。
当然,处理所有数据的诀窍是解析它们,以便它们最终出现在正确的位置。我们将在下一节中讨论这个问题,我们将描述控件的 PHP 代码。
清单 15-6 显示了包含产品控制数据的 JSON 对象。
清单 15-6。包含产品控制数据的 JSON 对象
<?php $productJSON = '{ "title":"The Worlds Best Laptop", "img":"/img/laptop1.jpg", "imgAlt" : "This is a picture", "priceStack" : [ {"label":"Retail Price", "currencySymbol" : "$", "wholeNumber": "1599", "remainder":"99"}, {"label":"Discount", "currencySymbol" : "$", "wholeNumber": "100", "remainder":"00"}, {"label":"Your Price", "currencySymbol" : "$", "wholeNumber": "1499", "remainder":"99"} ], "shipDate": {"shipLabel":"' . mLink("Estimated Ship Date", "javascript:;", "help", NULL, "return", NULL, "You will receive a definitive date during checkout") . '", "shipDate":"July 25, 2013"}, "descText": "Sartorial williamsburg small batch helvetica mixtape wayfarers. Art party biodiesel before they sold out authentic.", "descBullets": [ {"bulletText": "Squid pinterest carles, fingerstache forage scenester."},
{"bulletText": "Pinterest carles, fingerstache forage scenester."}, {"bulletText": "Carles, fingerstache forage scenester."} ], "button" : {"text": "Buy Me", "href": "/addToCart.php", "type": "primaryShadow"} }'; ?>
现在你已经看到了控件的输出、CSS 和数据,是时候看看完成所有工作的 PHP 代码了。
控制
虽然这是一个相当大的列表,但构成产品堆栈的函数背后的概念实际上相当简单:我们将属于控件的数据与最终包含该数据的 HTML 元素混合在一起。为此,我们构建一个大字符串,它包含 HTML 并在适当的位置包含所有适当的数据值。这就是我们所有的控件所做的一切,但它值得重复,因为人们有时会过度思考控件应该做什么,最终给自己带来很多麻烦。
正如我们已经提到的,我们在这个例子和我们的示例网站上使用 PHP。然而,任何可用于 web 开发的语言都同样适用(甚至更好,如果你碰巧对另一种语言比对 PHP 更熟悉的话)。我们认为这个想法也值得重复,因为我们希望您认识到算法(构建一个包含满载数据的 HTML 的字符串)是重要的部分。用于编码控件的语言并不重要——当然,只要它能完成工作。
构建产品控制的功能称为productStack
。它有两个参数:一个名为$obj
的对象,包含数据;一个名为$treatment
的字符串,指示创建垂直布局还是水平布局。$treatment
参数的唯一有效值是pcTreatment1
(对于垂直布局)和pcTreatment2
(对于水平布局)。任何其他值都将导致客户浏览器中出现令人讨厌的输出。如果我们更谨慎一点,我们会验证$treatment
参数的值,并以某种优雅的方式失败(比如不显示任何内容,而是将一条错误消息写入日志文件)。
如果你通读这个函数,你会看到我们首先创建了一个名为$output
的变量。我们通过将最外层元素类(一篇文章)的开始标签放入$output
变量中来启动这个函数。该函数的其余部分大部分由一系列检查数据对象内容的“if”语句组成。如果我们没有获得数据对象中某个特定名称的值,我们就不会将那部分 HTML 添加到输出字符串中。最后,我们添加最外层元素的结束标记,并将输出回显到浏览器,这导致调用函数的行被我们在那个长字符串中构建的 HTML 所替换。
很简单,真的。但是,因为产品控件有很多可能性——一个图像、多个价格值、运输信息、多个描述性元素等等——所以列表看起来可能很复杂。请记住,我们正在构建一个保存 HTML 的字符串,方法是检查我们的数据对象中是否存在每种数据,如果发现了这种数据,就追加更多的元素。同样,这是构建控件的基本算法。
注意,我们注释掉了检查按钮所需数据的if
语句。什么样的商品列表没有购买按钮?(其实我们可以想出几个这样的用例,但不会下那个兔子洞。这是我们在这个例子中不需要的复杂性。)
清单 15-7 显示了我们用来创建产品控件的 HTML 输出的 PHP 代码。
清单 15-7。产品控件背后的 PHP 代码
<?php function productStack($obj, $treatment) {
`// Start with our containing element and add a product control treatment if applicable
\(output = ' <article class="productControlWrap '.\)treatment.'">';
// If there's an image defined output the image tag
if(\(obj->img != NULL){
\)output .= ' ';
}
// If there's a title defined output it
if(\(obj->title != NULL){
\)output .= '
'.\(obj->title.'</h2>';
}
// If there's a priceStack defined output it
if(\)obj->priceStack != NULL){
\(output .= '<div class="pcPriceStack">';
for (\)i=0; \(i < sizeof(\)obj->{'priceStack'}); \(i++) {
// We create a series of price controls here. See chapter 14 for how price controls work.
\)output .= price(
\(obj->priceStack[\)i]->{'label'},
\(obj->priceStack[\)i]->{'currencySymbol'},
\(obj->priceStack[\)i]->{'wholeNumber'},
\(obj->priceStack[\)i]->{'remainder'},
"return"
);
}
\(output .= '</div>';
}
// If there's a shipDate defined output it
if(\)obj->shipDate != NULL){
// the shipDate function is an instance of our shipping control. See chapter 14.
\(output .= ' <div class="pcShippingStack">'. shipDate(\)obj-
shipDate->shipLabel, \(obj->shipDate->shipDate, "return") . '</div>'; } // If there's descriptive text or bullets defined output the parent element if(\)obj->descText != NULL || \(obj->descBullets != NULL){ \)output .= '
';';
}
// If there's descriptive text defined output it
if(\(obj->descText != NULL){ \)output .= '';';
$output .= \(obj->descText; \)output .= '
}
// If descriptive bullets are defined, output them
if(\(obj->descBullets != NULL){ \)output .= '';
for ($i=0; \(i < sizeof(\)obj->{'descBullets'}); \(i++) { \)output .= '- '.\(obj->descBullets[\)i]->{'bulletText'}.'
';
}
\(output .= '</ul>'; } // Closing tag for the .pcDescWrap if(\)obj->descText != NULL || \(obj->descBullets != NULL){` ` \)output .= '
}
// If there's a button defined output it
//if(\(obj->button != NULL){ // We're using the button control here. See Chapter 13 for more about button controls. \)output .= button(
$obj->button->{'text'},
$obj->button->{'href'},
NULL,
NULL,
\(obj->button->{'type'}, NULL, NULL, NULL, NULL, "return" ); //} \)output .= ' ';
echo $output;
}
?>`
总结
本章讲述了电子商务网站的另一个基本控制。一个电子商务网站必须有产品列表和购买这些产品的方法。产品控制满足这两个业务目标。
在本章中,我们展示了几项关键技术:
- 在控件中使用控件;
- 使用数据对象为控件提供值;
- 通过类名将 CSS 附加到控件的输出 HTML
- 实现使控件工作的基本算法。
由于该算法是我们的控件如何工作的核心,我们想在本章结束时再次提醒您。使控件工作的本质是构建一个 HTML 字符串,该字符串包含网页上所需的元素和数据。当访问者在 web 浏览器中查看网页时,该字符串将替换您在网页中放置的功能。虽然这不是迈克尔所说的火箭手术(他最喜欢的短语之一),但这是一种非常强大的创建网页的方法。
十六、表格控件
作为一名前端开发人员,Michael 对表格又爱又恨。一方面,表格提供了一种近乎完美的方式来显示表格数据。另一方面,一些开发人员滥用表格作为几乎任何事情的布局机制。Michael 经常看到开发人员(通常是被迫做前端工作的后端或中间件开发人员)使用嵌套表格来移动内容几个像素。对于知道它应该是什么的人来说,看到这种事情是非常令人沮丧的。我们相信许多读者都有同感。
我们假设你以前听过“表格是布局的恶魔”这种说法。为了火上浇油,我们有了响应式设计。除了不使用表格进行布局的所有其他原因(包括较低的性能、要呈现的 HTML 元素的增加以及难以阅读的代码),还有一个额外的问题是表格不能响应响应式设计。桌子是内部固定的;他们不允许对 TD 元素重新排序(就像 CSS 一样)。例如,如果一个有两栏的表格(左边是导航栏,右边是内容栏)用于基本的页面布局,那么这个页面就不容易重新排列。如果不从服务器创建一个新页面,导航就不能移动到顶部或右侧(这会对性能产生巨大影响)。
那么表和性能呢?在前两段中已经提到了几个与性能相关的问题,现在让我们更深入地探讨一下这个问题。就客户端(即浏览器)性能而言,表的一个大问题是,当浏览器呈现它们时,它们通常会强制执行一个或多个重绘操作。即使您遵循了在页面顶部加载样式、在页面底部加载脚本的原则,即使您的表格具有相当规则的内容,它们也经常会触发重绘事件。这些重绘事件大大降低了渲染速度(从而降低了页面加载时间),因为渲染引擎每次都必须从表的顶部开始。在服务器端,如果你为每个布局使用不同的页面(例如,垂直和水平),一些糟糕的访问者将手机旋转 90 度会导致一个全新的页面加载。除了让访问者抓狂之外,您还添加了一整套 HTTP 请求并增加了带宽使用,这都是因为有人希望看到您的信息得到更广泛的显示。最后,如果您创建一个接受数据流并生成一个表的控件,开发人员的性能会得到提高。即使手工编写一个小表也需要时间,至少与将数据从数据库传递给函数相比是这样。
由于性能问题(特别是在这种情况下的开发人员性能)和所有其他问题,我们重申许多其他人的建议:不要使用表格进行布局。但是如何在移动设备中正确使用表格——表格数据?这方面的问题是水平空间有限;大多数表格都需要向右滚动——这在网络上是不允许的,除非你用的是 Windows phone。因为我们遵循移动优先的模式,所以我们设计所有的页面,让它们在手机上看起来尽可能的好。因此,我们将分享 Michael 想出的解决这个问题的技术:用 CSS 样式的嵌套列表来呈现表格。听起来熟悉吗?这就是我们在第七章中构建导航的方式,当然,在这里我们使用不同的风格。
在本章中,我们将展示如何构建一个示例表格控件。您可以在我们示例站点上找到该控件:[
clikz.us/BookExamples/tableControl.php](http://clikz.us/BookExamples/tableControl.php)
在开始描述控件如何工作之前,我们认为应该向您展示它在两种状态下的样子:宽和窄。(同样,我们称每种状态为一种治疗,就像我们控制的所有变化一样。)我们还将在表格中显示一个突出显示的单元格。
图 16-1 显示了我们的表格控件的宽处理。虽然这看起来像一个标准表,但它实际上是一个包含一堆列表(ul
元素及其li
子元素)的div
元素。它的行为和外观都像一个标准表。在控件内部,我们展示了一种将表格转换为标准表格元素的方法。我们提供了使用表格元素的能力,因为 IE8 之前的 Internet Explorer 不支持显示表格数据的嵌套列表方法。我们使用浏览器检测来显示 IE6 和 IE7 的正确内容。
图 16-1。我们样品台的宽布局
图 16-2 显示了我们的悬停功能。当访问者的鼠标悬停在一个单元格上时,整行高亮显示,鼠标悬停的单元格进一步高亮显示(变暗)。在突出显示行和单元格时,与访问者正在考虑的选择相关的细节也被突出显示。正如我们在本章后面所展示的,所有这些突出显示都是通过 CSS 来控制的。
图 16-2。带有高亮(深色)单元格的宽布局
图 16-3 显示了我们的表格控件的窄(因此更高)处理。这种处理可能对游客在手机或其他有限显示设备上查看桌子有用。当页面设计者希望将表格信息放在页面的一侧,而将其他信息放在页面的另一侧时,窄视图也很有用。例如,一个音乐网站可能有一个歌曲和演唱这些歌曲的艺术家的列表,并附有这些歌曲的共同点的描述。
注意正如我们之前提到的,随着设备的快速增长,我们不能只计划手机和桌面浏览器。据我们所知,有人可能会在冰箱门上或柜门内侧或我们想象不到的地方看到我们的餐桌控制。因此,我们根据布局特征(窄与宽)而不是目标设备(桌面与电话)来思考(并命名我们的控制和处理)。
图 16-3。我们表控的狭隘待遇
除了改变类名以反映预期的处理之外,窄处理使用与宽处理完全相同的 HTML。我们刚刚使用 CSS 为窄显示屏设备旋转了表格。说到 HTML,让我们看看我们的例子背后的 HTML。
HTML
我们示例表格的 HTML 是由表格控件生成的。如前所述,它由一个div
元素组成,其内容是一组无序列表(宽布局中每行一个列表,窄布局中每个块一个列表)。
当您阅读 HTML 时,请注意div
元素上的类名。它们表明这个div
包含一个表的内容以及使用哪种处理方式(tableTreatment1
是宽处理方式,tableTreatment2
是窄处理方式)。类似地,ul
元素中的样式名表明一行是标题行还是正文行。另外,li
元素中的样式名称表明该元素是一个单元格,以及任何给定的单元格是否是第一个单元格。在li
元素中,data-colhead
属性(一个自定义属性)标识单元格所属的列。我们使用 data-colhead 属性的值来提供窄处理中的标题信息,这支持表的旋转。
注意 如果包含我们的样本站点上使用的所有数据,清单 16-1 会很长。制作一个像样的样本需要相当多的数据,但是这导致了清单中的大量重复。因此,我们只包括定义标题行的列表和定义第一行内容的列表。第一个列表定义了标题行。第二个列表定义了第一个正文行。因为正文行在结构上是相同的,只是内容不同,所以您可以通过检查其中一行来了解它们是如何工作的。要查看整个列表并查看实际表格,请转到
[
clikz.us/BookExamples/tableControl.php](http://clikz.us/BookExamples/tableControl.php)
清单 16-1 展示了本章前面展示的样本背后的 HTML。
清单 16-1。我们的样本表的 HTML】
<div class="table tableTreatment1"> <ul class="tr tableHead"> <li class="td " data-colhead=""></li> <li class="td " data-colhead="The Head"> The Head </li> <li class="td " data-colhead="Top of the Stack"> Top of the Stack </li> <li class="td " data-colhead="Don't Forget Me"> Don't Forget Me </li> <li class="td " data-colhead="Austin blog"> Austin blog </li> <li class="td " data-colhead="The Best for Last"> The Best for Last </li>
`
Irony mumblecore
Letterpress authentic
Salvia hella raw
Austin blog bicycle
Chambray 8-bit post-ironic
Master cleanse hoodie
数据对象
既然您已经看到了示例表后面的 HTML,那么让我们继续看 HTML 中的数据。表格控件将数据转换成 HTML,类似于我们在清单 16-1 中看到的 HTML。table 控件需要一个数组的数组,其中第一个内部数组保存标题行的数据(注意第一个值是空字符串),其余的内部数组保存正文行的数据。当然,在更现实的场景中,您可以通过查询数据库并为您构建列表的函数或对象从数据库中获取这些数据。因为我们的示例站点没有使用数据库(重点只放在本书的前端),所以我们使用静态数据。
清单 16-2 显示了我们的样本表背后的数据。
清单 16-2。进入我们样本表的数据
<?php $tableObj = '[ ["","The Head","Top of the Stack","Don\'t Forget Me","Austin blog", "The Best for Last"], ["Irony mumblecore","Letterpress authentic","Salvia hella raw","Austin blog bicycle","Chambray 8-bit post-ironic","Master cleanse hoodie"], ["Cosby sweater cred","Chambray 8-bit post-ironic","Letterpress authentic","Austin blog bicycle","Irony mumblecore","Master cleanse hoodie"], ["Chambray 8-bit post-ironic","Letterpress authentic","Austin blog bicycle","Synth jean shorts","Cosby sweater cred","Cosby sweater cred"], ["Salvia hella raw","Cosby sweater cred","Master cleanse hoodie","Irony mumblecore","Austin blog bicycle","Letterpress authentic"], ["Master cleanse hoodie","Chambray 8-bit post-ironic","Irony mumblecore","Austin blog bicycle","Letterpress authentic","Salvia hella raw"],
["Austin blog bicycle","Letterpress authentic","Salvia hella raw","Cosby sweater cred","Irony mumblecore","Chambray 8-bit post-ironic"] ]'; ?>
表格控件
如前所述,我们在示例站点中使用了 PHP。然而,任何支持 web 开发的语言都同样适用。在我们自己的工作中,多年来我们一直使用 Java 和 C#以及其他语言。
在控件的顶部,我们检查参数legacyBrowser
的值,并将我们编写的元素设置为div
、ul
和li
(对于大多数浏览器)或table
、tr
和td (for versions of Internet Explorer prior to IE8)
。
控件的大部分其余部分由一对嵌套的for
循环组成,这些循环遍历数据并写入适当的元素及其属性和内容。控件的最后一部分将 HTML 回显到浏览器。
清单 16-3 显示了构成表格控件的 PHP。
清单 16-3。定义表格控件的 PHP 代码
`<?php
function tableControl($model, $treatment, \(legacyBrowser) {
// We use ternary operator to set whether our
// table will use conventional table tags
// or our divs, uls, and lis since the CSS
// for the nontraditional elements aren't supported
// in IE7\. We can use browser detection to turn
// on traditional table elements for those browsers
// that can't support this new method.
\)tableElement = (\(legacyBrowser == TRUE ? "table" : "div");
\)trElement = (\(legacyBrowser == TRUE ? "tr" : "ul");
\)tdElement = ($legacyBrowser == TRUE ? "td" : "li");
$output = '<' . $tableElement . ' class="table ' . \(treatment . '">';
for (\)i = 0; \(i < sizeof(\)model); \(i++) {
if (\)i == 0) {
\(rowClass = "tableHead";
} else {
\)rowClass = "tableBody";
}
$output .= '<' . $trElement . ' class="tr ' . \(rowClass . '">';
for (\)j = 0; \(j < sizeof(\)model[$i]); \(j++) {
// Add a class of 'first' if it's the first column but not the header row.
\)first = (($j == 0 && \(i > 0) ? "first" : "");
// We're iterating through the 'td's here but also adding in the column head text as a
//data attribute in case we
// want it later. You can put a conditional around this and turn it on only if you require
//it to save bytes.
\)output .= '<' . $tdElement . ' class="td ' . $first . '" data-colhead="' . \(model[0][\)j] .
'">' . \(model[\)i][$j] . '</' . \(tdElement . '>';` ` }
\)output .= '</' . \(trElement . '>';
}
\)output .= '</' . $tableElement . '>';
echo $output;
}
?>`
实际的控制非常简单。真正的工作由风格来完成。让我们看看他们。
款式
我们用 CSS 样式做所有的布局工作。如前所述,这样做有很多好处。首先,尽可能多地使用 CSS 可以提高访问者的性能,因为 CSS 的呈现是浏览器固有的(而不是解释脚本,后者要慢得多)。
第二,我们可能能够更好地利用缓存。如果访问者已经查看了带有使用我们的表格样式表的表格的页面,那么这些样式就在缓存中,从而节省了大量的字节(提高了访问者的页面加载时间,降低了我们虚构公司的带宽成本)。因此,我们希望通过 CSS 做尽可能多的工作。这就是为什么 CSS 样式是我们表格控件中最大的部分。如果样式是动态组装的(从一个动态构建每个页面的系统中),我们就失去了缓存 isi 的好处,所以考虑如何构建我们的文件(HTML 和 CSS)是值得的,这样我们就可以从缓存中获得最大的好处。
注意在我们的示例站点上,CSS 样式在一个文件中。我们将它们分成几个清单,这样我们就可以对每个部分做一些解释。
所有治疗的风格
清单 16-4 到 16-6 显示了适用于表格控件所有处理的规则。如果我们添加了第三个处理,这些规则将适用于第三个处理以及到目前为止创建的两个处理。
清单 16-4 将列表元素的填充和边距设置为 0(零),并将list-style
属性设置为 none(以避免在我们的表中得到任何不合适的项目符号)。
清单 16-4。控制表格控件外观的 CSS
.table ul, .table li { padding: 0; margin: 0; list-style: none; }
清单 16-5 展示了我们如何为组成一个表格的各种元素设置显示模式,从而得到一个表格、行和单元格。
清单 16-5。为定义表格的类设置显示模式
/* * This is how we achieve our tablelike structure with these elements. * Basically we're just assigning the same attributes that the corresponding * table elements have in the 'display' property. */ .table { display: table; } .tr { display: table-row; } .td { display: table-cell; }
清单 16-6 显示了我们如何为表格控件生成的表格设置边框。
清单 16-6。控制表格的边框
.table, .tr, .td { border: 1px solid #CCC; border-collapse: collapse; }
款式为宽大处理
清单 16-7 到 16-24 包含了定义表格控件宽处理外观的规则。
清单 16-7 展示了我们如何设置表格的宽度和表格外侧的阴影。在这种情况下,我们为表格(名为tableTreatment1
)的宽处理设置这些值。
清单 16-7。设置宽处理的桌子宽度和阴影
/* * Table Treatment 1 */ .tableTreatment1 { width: 800px; -webkit-box-shadow: 2px 2px 2px #999999; -moz-box-shadow: 2px 2px 2px #999999; box-shadow: 2px 2px 2px #999999; }
清单 16-8 为宽处理设置最左边单元格的外观。特别是,它将背景设置为浅蓝色,将字体粗细设置为粗体。
清单 16-8。为宽处理样式化最左边单元格的内容
.tableTreatment1 .first { background: #c8dfff;
font-weight: bold; }
清单 16-9 展示了我们如何设计身体细胞的样式。为此,我们使用了一个很少使用的 CSS 选择器(nth-child
选择器)。这样做可以让我们对每隔一行设置不同的样式,从而使表格更具吸引力,更易于阅读。我们在评论中添加了额外的解释。
清单 16-9。塑造身体细胞
/* * By using the nth-child selector (which works in all * modern browsers), we can give our table a striped look. * Normally, you'd do this on the server side or with * JavaScript. Alternating the colors of the rows improves * readability and is (we think) more attractive. */
清单 16-10 显示了我们如何设置高亮单元格的颜色。
清单 16-10。改变高亮单元格的颜色
.tableTreatment1 .tableBody.tr:nth-child(odd) { background: #eef3fe; }
清单 16-11 展示了我们如何设置访问者鼠标悬停的行的颜色。同样,我们使用了nth-child
选择器来决定突出显示哪一行。请注意,我们设计奇数行的样式,但从不设计偶数行的样式。这是因为另一个规则(在清单 16-14 中)设置了所有行的悬停状态。那么这个规则会覆盖那个规则(因为这个规则更具体)来设置奇数行的悬停状态。我们将在这组规则中反复看到这种安排,我们将在本节的最后讨论这种技术的一个常见陷阱(以及如何避免它)。
清单 16-11。设置一行的悬停状态
.tableTreatment1 .tableBody.tr:nth-child(odd):hover { /* * We add a hover state to the odd .tr's that are also of * class .tableBody. This effect highlights the row the * visitor is hovering over with a slightly different * background color. However, since we want to maintain * the alternating row effect, this effect will be slightly * different from that on the even rows. */ background: #cad0f8; }
清单 16-12 展示了当高亮显示时,我们如何让第三列的单元格变成深绿色。注意,我们使用nth-child
选择器来指定奇数行和特定的列(第三列)。
清单 16-12。设置第三列的悬停颜色
.tableTreatment1 .tableBody.tr:nth-child(odd):hover .td:nth-child(3) { /* * We also use the nth-child selector to target columns.
* In this case, we target the third column of each odd row. * This rule complements the next rule, which makes the * entire 3rd column a green color but makes the alternating * rows a slightly darker green color to maintain readability. */ background: #54d543; }
清单 16-13 展示了我们如何使第三列的奇数行比第三列的偶数行稍微暗一点。我们再次使用nth-child
选择器来指定奇数行和特定的列(第三列)。
清单 16-13。把第三列的奇数行变成稍微暗一点的绿色
.tableTreatment1 .tableBody.tr:nth-child(odd) .td:nth-child(3) { /* * To maintain the integrity of our different-color column, * we make the highlighted cell go to a stronger green color * when the visitor hovers on that row. This rule selects only * the odd rows in the green column. */ background: #8ce981; }
清单 16-14 为正文行设置基本悬停高亮效果。同样,它实际上是为没有另外指定的行设置高亮效果,但效果是为偶数行定义悬停效果。
清单 16-14。为车身行设置基本悬停高亮
.tableTreatment1 .tableBody.tr:hover { background: #eeeff0; color: #1d80fd; }
清单 16-15 为第三列中的单元格设置悬停颜色,使得该列中高亮显示的单元格的背景颜色为深绿色。
清单 16-15。设置第三列单元格的悬停颜色
.tableTreatment1 .tableBody.tr:hover .td:nth-child(3) { background: #76f564; }
清单 16-16 使第三列变成绿色。因为将第三列的奇数行设置为较暗的绿色的规则具有更高的特异性,所以该规则将偶数行设置为较浅的绿色,这是我们用作第三列的基本颜色。
清单 16-16。使第三列变绿
.tableTreatment1 .tableBody.tr .td:nth-child(3) { /* * Now we make the entire third column a slightly green
* background color. Because the previous nth-child * selector has greater specificity, it will override this * rule in odd rows. */ background: #b2ebac; }
清单 16-17 显示了我们如何设置单元格的外观(不包括背景颜色),包括标题单元格和主体单元格。
清单 16-17。单元格样式(不包括背景色)
.tableTreatment1 .td { padding: 4px; font-size: 12px; vertical-align: middle; }
清单 16-18 显示了我们如何设置高亮单元格的前景色和背景色。前景色变成白色,背景色变成强烈的蓝色。因为第三列的悬停规则(下一个规则,在清单 16-19 中)具有更大的特异性,所以这个规则不适用于第三列。
清单 16-18。为除第三列之外的所有列设置悬停状态
.tableTreatment1 .tableBody .td:hover { color: white; background: #1d80fd; }
清单 16-19 展示了当访问者的鼠标悬停在第三列的单元格上时,我们如何设置该单元格的前景色和背景色。指令确保当访问者的鼠标停留在第三列的单元格上时,它们总是变成深绿色。
清单 16-19。第三列单元格的悬停规则
.tableTreatment1 .tableBody:hover .td:nth-child(3):hover { color: white; background: #12ba00 !important; }
清单 16-20 控制标题行的外观。它将文本设置为白色和粗体,设置深灰色文本阴影,并设置浅灰色背景色。此外,对于大多数浏览器,它使用 SVG 图像来设置页眉的背景图像。最后,它为大多数浏览器设置了渐变效果。最终效果是深色渐变背景上的白色文本。
清单 16-20。宽处理的表头造型
.tableTreatment1 .tableHead { color: white; font-weight: bold; text-shadow: 0 0 3px #333; background: #aebcbf; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw
Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2FlYmNiZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUw JSIgc3RvcC1jb2xvcj0iIzZlNzc3NCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUxJSIgc3RvcC1j b2xvcj0iIzBhMGUwYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMw YTA4MDkiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9 IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #aebcbf), color-stop(50%, #6e7774), color-stop(51%, #0a0e0a), color-stop(100%, #0a0809)); background: -webkit-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: -o-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: -ms-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: linear-gradient(to bottom, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#aebcbf', endColorstr='#0a0809', GradientType=0); }
清单 16-21 只是将标题行中每个单元格的内容居中——这很简单,但对标题中的内容对齐很有必要。
清单 16-21。将标题行的单元格居中
.tableTreatment1 .tableHead .td { text-align: center; }
清单 16-22 显示了当访问者使用 IE9 时,我们在哪里关闭我们在清单 16-21 中创建的过滤器。IE9 不需要那个滤镜,所以我们在这条规则里把它关了。
清单 16-22。为 IE9 关闭标题中的过滤器
.ie9 .tableTreatment1 .tableHead { filter: none; }
清单 16-23 显示了当访问者使用 IE8 时,我们如何关闭我们在清单 16-21 中创建的过滤器和背景。Internet Explorer 需要为每个单元格设置背景,而不是为整行设置背景,所以我们将在清单 16-24 中这样做。
清单 16-23。关闭 IE8 的滤镜和背景
.ie8 .tableTreatment1 .tableHead { filter: none; background: none; }
清单 16-24 显示了当访问者使用 IE8 时,我们如何为标题单元格设置背景和过滤。IE8 不会对行使用背景和过滤器定义,所以我们必须在单元格上设置这些值。
清单 16-24。为 IE8 中的标题单元格设置背景
.ie8 .tableTreatment1 .tableHead .td { background: #aebcbf; filter: progid:dximagetransform.microsoft.gradient(startColorstr='#aebcbf', endColorstr='#0a0809', GradientType=0); }
款式为窄幅处理
窄处理获得的样式与定义宽处理外观的样式有很大不同。首先,窄处理为控件数据中的每个数组创建了一组行,而不是单个行。此外,第二种处理没有突出悬停效果。由于触摸屏(无论是手机还是冰箱)没有鼠标指针浮动,设置悬停效果没有意义。因此,狭义处理的规则要简单得多。清单 16-25 到 16-32 显示了定义我们的表格控件的窄处理的规则。
清单 16-25 显示了我们如何将窄表格的宽度设置为 320 像素。
清单 16-25。设定窄治疗的宽度
/* * Table Treatment 2 */ .tableTreatment2 { width: 320px; }
清单 16-26 显示了当我们使用窄处理时,我们如何关闭 HTML 中所有元素的边框。
清单 16-26。关闭窄边框处理
.tableTreatment2 .table, .tableTreatment2 .tr, .tableTreatment2 .td { border: none; }
清单 16-27 显示了我们如何关闭标题内容的显示,因为我们在窄处理中没有使用标题单元格。
清单 16-27。关闭割台单元进行窄处理
.tableTreatment2 .tableHead { display: none; }
清单 16-28 显示了我们如何在表格控件的窄处理中将一行的宽度设置为 200 像素。
清单 16-28。将行宽设置为 200 像素
.tableTreatment2 .tr { width: 200px; }
清单 16-29 展示了我们如何在表格控件的窄处理中设计单元格的样式。
清单 16-29。对狭窄治疗区内的细胞进行造型
.tableTreatment2 .td { display: block; float: none; padding: 5px; }
清单 16-30 展示了我们如何对表格控件的窄处理的每个块中的第一个单元格进行样式化。我们在宽处理中使用与标题行相同的样式。first-child
选择器让我们只指定窄处理中每个块的第一个单元格。
清单 16-30。对每个区块内的第一个单元格进行窄处理
.tableTreatment2 .td:first-child { /* * By using the first-child selector, we can target * the first TD, which is a relevant pivot point for our data. */ color: white; font-weight: bold; text-shadow: 0 0 3px #333; background: #aebcbf; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2FlYmNiZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUw JSIgc3RvcC1jb2xvcj0iIzZlNzc3NCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUxJSIgc3RvcC1j b2xvcj0iIzBhMGUwYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiMw YTA4MDkiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9 IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #aebcbf), color-stop(50%, #6e7774), color-stop(51%, #0a0e0a), color-stop(100%, #0a0809)); background: -webkit-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: -o-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: -ms-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: linear-gradient(to bottom, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#aebcbf', endColorstr='#0a080 9', GradientType=0); }
清单 16-31 展示了我们如何在单元格的常规内容前插入data-colhead
属性的值、一个冒号和一个空格。我们还将所有插入的内容设置为粗体和中深灰色。这种技术为每个单元格提供了相当于标题的内容。
清单 16-31。在窄处理中插入相当于单元格标题的内容
.tableTreatment2 .td:before { /* * We're able to leverage the content: attr() again as we did * in our link control. Since our control has stored the * value of the column in the 'td' we can just get it from there. */ content: attr(data-colhead) ": "; font-weight: bold; color: #777; }
清单 16-32 显示了我们如何覆盖每个块中第一行的文本插入。因为我们不想在标题前以冒号和空格结束,所以我们需要将第一个设置为空字符串(" ")。
清单 16-32。确保第一行没有无意义字符
.tableTreatment2 .td:first-child:before { /* * Since we don't need the header data for the * first TD, we override it so we don't get the ': '. */ content: ""; }
添加媒体查询
我们认为这种控制很适合媒体询问。对于使用窄设备的访问者,很自然地会想到将布局从宽改为窄。如果你想给你的表格控件添加媒体查询,清单 16-33 中的规则显示了如何做。我们不单独描述它们,因为它们都与我们已经描述过的规则非常相似。当您使用这组规则时,您可以将它们添加到支持表格控件的 CSS 文件中的任何位置。
/* Mobile Layout */ @media only screen and (max-width: 767px) { .tableTreatment1 { width: 320px; } .tableTreatment1 .table, .tableTreatment1 .tr, .tableTreatment1 .td { border: none; } .tableTreatment1 .tableHead { display: none; } .tableTreatment1 .tr { width: 200px; } .tableTreatment1 .td { display: block; float: none;
padding: 5px; } .tableTreatment1 .td:first-child { color: white; font-weight: bold; text-shadow: 0 0 3px #333; background: #aebcbf; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodH RwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcH Jlc2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncm FkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3 Agb2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2FlYmNiZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9Ij UwJSIgc3RvcC1jb2xvcj0iIzZlNzc3NCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUxJSIgc3RvcC 1jb2xvcj0iIzBhMGUwYSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9Ii MwYTA4MDkiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdG g9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #aebcbf), color-stop(50%, #6e7774), color-stop(51%, #0a0e0a), color-stop(100%, #0a0809)); background: -webkit-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%) ; background: -o-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: -ms-linear-gradient(top, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); background: linear-gradient(to bottom, #aebcbf 0%, #6e7774 50%, #0a0e0a 51%, #0a0809 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#aebcbf', endColorstr='#0a 0809', GradientType=0); } .tableTreatment1 .td:before { content: attr(data-colhead) ": "; font-weight: bold; color: #777; } .tableTreatment1 .td:first-child:before { content: ""; } }
这些规则产生了一个类似于狭义处理的表。然而,媒体查询中的规则实际上修改了宽处理(tableTreatment1
)。结果是一个具有窄处理结构和宽处理配色方案的表格。图 16-4 显示了结果。
图 16-4。使用表格控件进行媒体查询的结果
对我们来说,为表格控件编写 CSS 规则很有趣。我们不常使用那些不常见的选择器,所以找个地方展示它们是如何工作的是很好的。许多表可能不需要这种复杂程度;根据我们的经验,将中间的一列变成不同的颜色是不常见的。然而,当你需要这样做的时候,选择符可以让你指定一个特定的孩子,甚至是孩子中的孩子。正如我们所说,这很有趣,但我们也很奇怪。
小心时连不是真的连。我们顺便提到偶数行没有被指定为偶数。相反,我们为所有行设置背景,然后覆盖奇数行的背景。我们可以使用
even
说明符来补充odd
说明符,但是它会添加我们不需要添加的字节。此外,最好避免不必要的说明符。除了节省字节,您可能会遇到这样的情况:使用看似互补的说明符,然后发现有些奇怪的情况与它们都不匹配。所以最好设定一个基础待遇,指定例外情况。在这种情况下,奇数和偶数就可以了。当存在两种以上的情况时,就会出现问题,例如序列中第一项的附加值以及偶数和奇数的值。
总结
在这一章中,我们已经展示了如何开发和使用一个表格控件。我们展示了,如果你使用响应式设计,你不能使用通常的表格元素(table
、tr
和td
)。然后我们展示了如何通过在一个div
元素中创建一组列表来制作一个没有这些元素的表格。之后,我们展示了如何使用代码(本例中是 PHP,但也可以是 Java 或 C#或任何支持 web 的语言)将数据转换成 HTML,我们可以对其进行样式化,使其成为表格。最后,我们描述了将这些列表转换成表格中的行和单元格的每种样式。
在这个过程中,我们演示了如何选择表中的单个行和单元格。我们用这种效果来改变前景和背景的颜色。但是,您也可以使用这些选择器来创建一个包含帮助内容或更多产品细节或任何您能想到的内容的iframe
元素,然后将iframe
元素放在高亮显示的单元格附近。我们确信选择器还有其他的用途,可以让你指定一个特定的孩子(或者一组孩子,在even
和odd
说明符的情况下),但是我们会把它们留给你自己去想象。
十七、选项卡控件
我们都在网上见过很多标签。有些还可以。有些发臭。很自然,我们希望制作一个不会发臭的选项卡控件(希望这比“好”要好),这样我们就可以在各种内容中重用它,同时确保它看起来不错,运行良好。
网站上的标签经常令人讨厌的一个原因是它们不能追踪访问者的位置。你有没有去过一个页面,导航到第三个标签找到你想要的,去了另一个网站,然后回到有标签的网站,却发现你又在第一个标签上?我们有,我们不喜欢它。更糟糕的是,如果访问者刷新页面,一些标签方案会跳回到第一个标签。真烦人。在一个相关的问题上,我们也希望标签,我们(和我们的访问者)可以链接。
为了在设计上有一点灵活性,我们希望标签沿着内容的顶部运行(即水平标签),标签沿着内容的左侧运行(即垂直标签)。在我们看来,一些内容似乎用水平或垂直标签更有意义。此外,我们希望我们的页面设计者(将所有这些控件放在一起制作网页的人)可以选择水平或垂直标签。因此,我们在一个对照中有两个处理(变异)。自然,我们希望我们的标签看起来不错。标签并不是有史以来最吸引人的网页设计元素,但这并不意味着它们必须是包含纯文本的方形盒子。因此,我们希望标签有圆角,渐变背景,文本水平和垂直居中。当一个或多个标签包含多行文本时,文本必须保持居中。最重要的是,我们希望标签在收缩和展开以适应它们的内容时是动态的。
注我们无法在一本书里展示一个动画效果。要以动画方式查看选项卡的大小变化,请在我们的示例站点上使用选项卡控件:
[
www.clikz.us/BookExamples/tabControl.php](http://www.clikz.us/BookExamples/tabControl.php)
最后但同样重要的是,我们希望标签表现良好。不幸的是,许多选项卡控件使用表格。正如我们在前一章所讨论的,表有许多性能问题。此外,我们严格要求表格只能用于表格内容。然而,标签是表格内容。那么,我们如何在不使用那些糟糕的表格元素的情况下获得类似表格的功能呢?幸运的是,CSS 提供了一种方法——通过display
属性。正如你将在本章后面的 CSS 清单部分看到的,我们使用display:table
将列表转换成表格,然后使用display:table-cell
将列表项转换成表格单元格。这种技术提供了更好的性能,因为浏览器的呈现引擎在开始呈现每个单元格之前不会等待一整行(或者更糟,整个表格)到达。这并没有很大的区别,除非您有很多表,或者更糟,表中有表。然而,这些小问题累积起来会导致糟糕的性能,因此我们尽可能地防止所有这些小的性能问题。正如我们在别处提到的,web 开发人员获得了更显著的性能提升。从数据库中检索数据并将其传递给一个函数(控件)要比每次需要时自己制作标签容易得多。此外,控件确保一致性,这在大多数网站上都是一个重要的问题。
现在我们有了一个列表,列出了我们希望标签做的事情(也就是说,我们对控件有了要求),我们可以继续描述如何让所有这些工作起来。在我们阅读这一章的时候,我们还会探索一些技术细节,我们必须实现这些细节才能使一切正常工作。不过,现在让我们先来看看输出。图 17-1 显示了我们的选项卡控件的水平处理。
图 17-1。选项卡控件的水平处理
为了多样化,水平处理使用白色背景,而垂直处理使用黑色背景。你可以很容易地使用其他颜色。图 17-2 显示了选项卡控件的垂直处理。
图 17-2。选项卡控件的垂直处理
HTML
像往常一样,控件不直接包含 HTML。相反,PHP 函数在页面加载时创建 HTML。不过,查看控件创建的 HTML 来为脚本 (JavaScript)和样式(CSS)信息提供上下文还是很有帮助的。HTML 在水平和垂直处理之间不会改变;我们完全通过 CSS 来控制外观上的差异。
HTML 由一个 section 元素组成,该元素有两个ul
子元素。第一个列表的li
孩子,其 class 属性包括tabTriggerLI
,持有标签。class 属性包含activeTab
的第一个列表的li
子列表持有当前打开的选项卡的标签。第二个ul
元素包含选项卡的内容(无论当前打开还是关闭)。class 属性包含tabContentLI
的li
元素包含选项卡的内容。class 属性包含showTab
的li
元素保存当前打开的选项卡的内容。清单 17-1 显示了在图 17-1 和 17-2 中显示的选项卡控件的 HTML,打开到第一个选项卡。
清单 17-1。标签页的 HTML】
<section data-item="tab" class="tabWrap tabTreatment1" id="tabID"> <ul class="tabTriggerUL"> <li class="tabTriggerLI activeTab"> <a href="#tab1" class="tabTriggerA">This is a Tab</a> </li> <li class="tabTriggerLI"> <a href="#tab2" class="tabTriggerA">The Trigger for a <br> Long Tab Two</a> </li> <li class="tabTriggerLI"> <a href="#tab3" class="tabTriggerA">Don't Forget Me</a> </li> <li class="tabTriggerLI"> <a href="#tab4" class="tabTriggerA">I Like Being a Tab</a> </li> </ul> <ul class="tabContentUL" style="height: 215px; "> <li class="tabContentLI showTab" data-tabid="#tab1"> <p> Skateboard banh mi direct trade fanny pack mixtape, pork belly art party. Dreamcatcher wes anderson raw denim kogi gastropub. </p> <p> Thundercats ennui carles iphone, pour-over photo booth quinoa leggings stumptown PBR fanny pack cliche gluten-free. </p> <p> Small batch tofu gluten-free, vinyl you probably haven't heard of them typewriter umami viral DIY four loko aesthetic. </p> </li> <li class="tabContentLI" data-tabid="#tab2"> <p> Carles viral yr, williamsburg letterpress ethnic gluten-free aesthetic american apparel ennui chambray polaroid you probably haven't heard of them. </p> <p>
` Scenester hoodie tattooed food truck tofu, selvage blog sriracha polaroid hella keytar
before they sold out +1.
Hella 8-bit viral master cleanse salvia shoreditch. Leggings banksy mustache, godard VHS
truffaut mixtape ethnic umami gluten-free occupy kale chips skateboard mcsweeney's small batch.
Swag carles terry richardson, chillwave sustainable pickled high life keffiyeh single-
origin coffee fanny pack kogi.
Carles farm-to-table stumptown, pitchfork williamsburg wes anderson whatever sartorial.
You probably haven't heard of them kogi kale chips, gluten-free scenester leggings pitchfork
authentic next level raw denim mcsweeney's dreamcatcher umami.
Swag carles terry richardson, chillwave sustainable pickled high life keffiyeh single-
origin coffee fanny pack kogi.
Carles farm-to-table stumptown, pitchfork williamsburg wes anderson whatever sartorial.
You probably haven't heard of them kogi kale chips, gluten-free scenester leggings pitchfork
authentic next level raw denim mcsweeney's dreamcatcher umami.
`
数据
在描述控件核心的函数时,我们提到了tabControl
函数的第一个参数是一个模型对象。因为我们使用 PHP,所以它必须是一个 PHP 对象。在这种情况下,它包含一个 JSON 对象。JSON 对象包含一组逗号分隔的值,每个选项卡对应一个值。每个值都包含一个逗号分隔的列表,其中包含每个选项卡的详细信息。换句话说,数据对象由外部逗号分隔列表中的逗号分隔列表组成。
提示
tabcontent
值包含 HTML。您可以使用这种机制将链接、图像或其他 HTML 元素插入到选项卡的内容中。
清单 17-2 显示了我们用来创建标签控件的数据对象。
清单 17-2。为我们的样本选项卡控件提供数据的对象
<?php $tabObj = '[ { "triggertext" : "This is a Tab", "triggerid" : "#tab1", "tabcontent" : "<p>Skateboard banh mi direct trade fanny pack mixtape, pork belly art party. Dreamcatcher wes anderson raw denim kogi gastropub. </p><p>Thundercats ennui carles iphone, pour-over photo booth quinoa leggings stumptown PBR fanny pack cliche gluten-free. </p><p>Small batch tofu gluten-free, vinyl you probably haven\'t heard of them typewriter umami viral DIY four loko aesthetic.</p>" }, { "triggertext" : "The Trigger for a <br>Long Tab Two", "triggerid" : "#tab2", "tabcontent" : "<p>Carles viral yr, williamsburg letterpress ethnic gluten-free aesthetic american apparel ennui chambray polaroid you probably haven\'t heard of them. </p><p>Scenester hoodie tattooed food truck tofu, selvage blog sriracha polaroid hella keytar before they sold out +1\. </p>" }, { "triggertext" : "Don\'t Forget Me", "triggerid" : "#tab3", "tabcontent" : "<p>Hella 8-bit viral master cleanse salvia shoreditch. Leggings banksy mustache, godard VHS truffaut mixtape ethnic umami gluten-free occupy kale chips skateboard mcsweeney\'s small batch.</p><p>Swag carles terry richardson, chillwave sustainable pickled high life keffiyeh single-origin coffee fanny pack kogi.</p><p>Carles farm-to-table stumptown, pitchfork williamsburg wes anderson whatever sartorial. You probably haven\'t heard of them kogi kale chips, gluten-free scenester leggings pitchfork authentic next level raw denim mcsweeney\'s dreamcatcher umami. </p>" }, { "triggertext" : "I Like Being a Tab", "triggerid" : "#tab4", "tabcontent" : "<p>Swag carles terry richardson, chillwave sustainable pickled high life keffiyeh single-origin coffee fanny pack kogi.</p><p>Carles farm-to-table stumptown, pitchfork williamsburg wes anderson whatever sartorial. You probably haven\'t heard of them kogi kale chips, gluten-free scenester leggings pitchfork authentic next level raw denim mcsweeney\'s dreamcatcher umami. </p>" } ]'; ?>
控制
毫无疑问,你现在已经习惯了(除非你因为某种原因跳到了这一章——如果你直接跳到了这里,请阅读第十章;它解释了我们为什么要做控件),实际的控件是一个 PHP 函数(本例中称为tabControl
)。该函数有三个参数,如表 17-1 所述。
tabControl
函数创建一个输出变量,它可以将控件生成的 HTML 写入其中。然后,它将 section 元素的开始标记及其所有属性(包括该选项卡控件的标识符)添加到输出变量中。接下来,该函数将保存选项卡控件标签的ul
元素的开始标签写入输出变量。然后一个for
循环将列表的内容写入输出变量(也就是说,它将包含标签的li
元素写入输出变量)。在写li
元素时,控件插入数据,包括属性和它们的值。然后,该函数关闭第一个列表,并启动第二个列表,其中包含选项卡的内容。另一个for
循环将包含选项卡内容的li
元素(及其属性)写入输出变量。然后,该函数将第二个列表的结束标记和 section 元素写入输出变量。最后,该函数将输出变量的内容(现在包含我们的 HTML,所有数据都在正确的位置)回显到浏览器。在浏览器中,我们的 CSS 和 JavaScript 可以与 HTML 交互,生成最终的选项卡控件供访问者使用。
注意
id
属性的插入支持链接到特定的标签页,并使各个标签页出现在浏览器的历史记录中,这样访问者就可以返回到特定的标签页(或者在刷新页面时返回到同一个标签页)。基本上,id
让浏览器记住每个标签作为一个单独的位置。
清单 17-3 显示了tabControl
函数。
清单 17-3。tab control 功能
<?php function tabControl($model, $id, $treatment) { $output = '<section data-item="tab" class="tabWrap ' . $treatment . '" id="' . $id . '">'; $output .= ' <ul class="tabTriggerUL">'; for ($i = 0; $i < sizeof($model); $i++) { $output .= ' <li class="tabTriggerLI">'; $output .= ' <a href="' . $model[$i] -> triggerid . '" class="tabTriggerA">' . $model[$i] -> triggertext . '</a>'; $output .= ' </li>'; } $output .= ' </ul>'; $output .= ' <ul class="tabContentUL">'; for ($j = 0; $j < sizeof($model); $j++) { $output .= ' <li class="tabContentLI" data-tabid="' . $model[$j] -> triggerid . '">' . $model[$j] -> tabcontent . '</li>'; }
` \(output .= ' </ul>';
\)output .= '';
echo $output;
}
?>`
创建选项卡控件
要创建选项卡控件,必须调用tabControl
函数。如前所述,制作选项卡控件的 PHP 函数有三个参数:数据、每个选项卡控件的唯一标识符和处理方式(水平或垂直)。标识符"tabTreatment1"
表示水平标签控件;"tabTreatment2"
表示垂直选项卡控件。然后,脚本会将选项卡放置在页面中。清单 17-4 展示了我们是如何创建标签控件的,这些控件在我们的示例站点和本章中被用作示例。
清单 17-4。创建我们的样本标签控件
`
`JavaScript
我们的很多控件都不使用 JavaScript。在选项卡控件中,它用于一点交互性:当访问者点击它的触发器时,让右边的选项卡出现。我们还使用 JavaScript 让选项卡控件在 IE7 中正确呈现。在本例中,我们创建了一个名为tab
的 jQuery 插件。该插件使访问者能够通过浏览器的后退按钮、浏览器的历史功能或特定选项卡的链接来打开特定选项卡。它还向每个选项卡添加了一个 click 事件,从而可以打开访问者单击的选项卡。它还控制当不同选项卡的内容大小不同时触发的动画效果。
我们没有在这里详细描述这个插件,而是在整个插件中进行了广泛的注释。这些注释构成了一个很长的列表,但是它们也将描述放到了一个更有意义的上下文中。清单 17-5 显示了标签控件的 jQuery。
清单 17-5。我们选项卡控件的 jQuery 插件
`;(function($) {
$.fn.extend({
//name of our plug-in
tab : function() {
return this.each(function() {
// Declare some pointers relative to the element passed
// into the plug-in.
var tab = $(this), hash = window.location.hash,
// Find the the .tabContentUL. We'll use this as a starting
// selector to find
our targeted tab content
tabContentUL = tab.find(".tabContentUL"),
// Grabs all the .tabContentLI's into a collection.
tabContentLIs = tabContentUL.find(".tabContentLI"),
// Grab a collection of tab trigger anchor tags.
tabTriggers = tab.find(".tabTriggerA"), tabTriggerLIs = tab.find(".tabTriggerLI");
// If a user has bookmarked this page, we want to open the
// tab he had open when bookmarking. First we'll check to see
// whether there's a hash left by using one of the tab
// triggers.
if (hash) {
var count = 0;
tabTriggers.each(function checkHash() {
// Because this variable is scoped within a function
// we can use 't' again to represent each tab trigger
// as we enumerate through the collection.
var t = $(this);
if (t.attr("href") == hash) {
// if the href matches the hash, add the activeTab
// class to the that triggers parent LI
t.parents(".tabTriggerLI").addClass("activeTab"),
// Then find the matching content tab and add
// the showTab class to that, displaying that tab.
currentTab = findTabContentLI(hash);
currentTab.addClass("showTab");
//Set the height of the tabContentUL for the bookmarked
// contentTab.
tabContentUL.height(currentTab.outerHeight(true));
} else {
// Add 1 to the count if there isn't a match
// to the hash. We'll use this later to trigger
// the first tab if we don't find a match for any
// of the elements.
count++;
}
});
// In case our page has changed since the user bookmarked it
// or if there are two tab controls on a page with varying
// tab trigger names, we'll handle the case where there's no
// matches and display the first tab. So if the count shows as
// many unmatched tabTriggers as there are tabTriggers in
// total, we'll show the first tab.
if (count == tabTriggers.length) {
showFirstTab();
}
} else {
// If there's no hash, show the first tab
showFirstTab();
}
// Add a click event to the tab targets.
$(this).find(".tabTriggerA").click(function tabTrigger() {
// Captures the element clicked and makes it a jQuery element.
// The $(this) here is scoped to only the .tabTriggerA that
// was clicked.
var t = $(this),
// We'll resuse the href as the identifier
// for our tab selection
tabId = t.attr("href"),
// Find the matching .tablContentLI with the same id attribute
// as the href from the target selected.
targetContentTab = findTabContentLI(tabId);
// Set all the tab content areas back to the default of
// display: none and opacity:0 by removing the showTab class
// from all of them.
tabContentLIs.removeClass("showTab");
// The same as above: Remove all the 'activeTab' classes from
// the tabTriggers to reset them.
tabTriggerLIs.removeClass("activeTab");
// Add a class to the LI containing the clicked tab trigger.
// We'll use this to style the active trigger.
t.parents(".tabTriggerLI").addClass("activeTab");
// For our progressive enhancement, we're going to use CSS3
// transitions to handle animating the height of our tab
// content wrap to match its contents, as well as animating
// the opacity to let the content fade in after the height
// animation is complete. However if the CSS3 animation isn't
// available we'll get the same effect by using Javascript.
if (Modernizr.cssanimations) {
tabContentUL.height(targetContentTab.outerHeight(true));
targetContentTab.addClass("showTab");
} else {
tabContentUL.animate(
// Declare what property to animate. In this case we're
// animating the height of the .tabContentUL to the same
// height as the targetContentTab plus padding (that's the
// outerHeight()).
{
"height" : targetContentTab.outerHeight() + "px"
},
// how long to complete the animation
300,
// callback after animation is complete.
function showTab() {
targetContentTab.addClass("showTab");
});
}
});
// Because IE still has some trouble being in compliance, we'll
// trigger a JS solution to allow for our tabs to be any height
// and still retain the tab style metaphor by being the same
// height.
if($("html").hasClass("ie")){
equalizeTriggerHeights();
}
// We'll find the tallest triggerLI and set the remaining LIs to
// that height.
function equalizeTriggerHeights(){
var maxHeight = 0;
tabTriggerLIs.each(function(){
var t = \((this);
var tHeight = t.height();
if(tHeight > maxHeight){
maxHeight = tHeight;
}
// As long as we're in here, we'll vertically center
// the text inside the trigger LIs. We'll accomplish
// this centering by positioning the text 50% from the
// top, which puts the top of the text box in the middle
// of the vertical height of our LI. But since that would
// look too low, we'll give the text a negative margin equal
// to 1/2 its height; that should do that trick. However,
// IE7 doesn't allow this trick, so we'll disable it for
// that browser. The reason we don't use CSS to set the
// margin is that we don't know how high the tab will be,
// because the tab may have more than one line of content.
if(!\)("html").hasClass("ie7")){
var tabTrigger = t.find(".tabTriggerA");
tabTrigger.css("margin-top", -(tabTrigger.outerHeight() / 2));
}else {
t.find(".tabTriggerA").css("position", "static");
// There's a display bug when IE7 first loads, we'll
// counteract that here.
setTimeout(function(){ie7Fix();}, 100);
}
})
tabTriggerLIs.height(maxHeight);
}
function ie7Fix(){
var showTab = tab.find(".showTab");
showTab.css("display" , "block");
tab.find(".tabContentUL").height(showTab.outerHeight(true));
}
// Finds the matching content based on the selector and returns
// the element.
function findTabContentLI(selector) {
selectedTabContent = tabContentUL.find(".tabContentLI[data-tabid='" + selector + "']");
return selectedTabContent;
}
// Shows the first tab in the series.
function showFirstTab() {
var currentTab = tabContentLIs.eq(0);
currentTab.addClass("showTab");
tabContentUL.height(currentTab.outerHeight(true));
tabTriggers.eq(0).parents(".tabTriggerLI").addClass("activeTab");
}
});
}
});
})(jQuery);`
设计选项卡控件的样式
我们制作控件的标准范例是为所有处理制作一组通用的 HTML 元素;我们再次接受了选项卡控件的范例。因此,不同治疗之间的差异完全取决于 CSS。
在选项卡控件的情况下,我们有两种处理方式:水平(由名称包含tabTreatment1
标识符的类表示)和垂直(由名称包含tabTreatment2
标识符的类表示)。除了适用于一种或另一种处理的规则之外,选项卡控件还包括一些适用于所有处理的规则。让我们从那些常见的规则开始。
所有治疗的风格
一些规则适用于选项卡控件的所有处理。目前我们有两种处理方式(横向和纵向)。如果我们要开发额外的治疗方法,这些规则也将适用于这些治疗方法。正如我们对其他复杂控件所做的那样,我们将分别描述每个规则。我们还在整个规则中加入了注释。
一般原则是将选项卡控件中的列表转换成表格。表格会自动在可用区域展开标签,并很好地将标签居中。所以那些表格元素确实有用处。
注意在我们的示例站点上,CSS 样式在一个文件中。我们将它们分成单独的列表,以便解释每条规则。
清单 17-6 显示了tabWrap
类,它为选项卡控件元素提供了一个包装器。该规则除了注释之外没有其他内容。它的存在是为了让其他规则(比如tabWrap ul
,我们将在清单 17-7 中看到)使用它作为后代说明符的一部分,并为 JavaScript 提供一个钩子,以便在制作选项卡控件动画时使用。因此,它没有工作内容,只有评论。
清单 17-6。设计标签控件的外包装
`/*
* These rules define the generic attributes that all instances of our tab controls have.
/
.tabWrap {
/
* In order to get the tabs to fit across the whole
* control and have the text inside the tab triggers
* be vertically centered, we use display:table and
* display:table-cell to set up a faux table, with the
* UL element being the table and the LI elements
* being the cells in the table.
*/
/*
* This class handles making the content tab
* visible and thus enables our animations.
*/
}`
清单 17-7 展示了我们如何在选项卡控件中设计两个列表的样式。我们将填充和边距值设置为零(0),并将position
属性设置为relative
。我们将把列表中其他元素的position
属性设置为absolute
,以控制选项卡及其内容在选项卡控件中的位置。
清单 17-7。标签控件内的列表样式
.tabWrap ul { padding: 0; margin: 0; position: relative; }
清单 17-8 展示了我们如何在每个列表中设置列表项的样式。我们将填充和边距设置为 0。我们还将list-style
属性设置为none
,这样我们就不会在输出中得到项目符号。
清单 17-8。标签控件内列表项的样式
.tabWrap li { padding: 0; margin: 0; list-style: none; }
清单 17-9 显示了我们如何将包含标签的列表转换成表格。因为我们将宽度设置为 100 %,所以选项卡控件现在填充了它的可用空间。
清单 17-9。把列表变成表格
.tabWrap .tabTriggerUL { display: table; width: 100%;
}
清单 17-10 显示了我们如何将每个标签转换成一个表格单元格。我们还设置填充值以及水平和垂直居中。
清单 17-10。将列表项变成表格单元格
.tabWrap .tabTriggerLI { display: table-cell; padding: 5px 20px 2px 20px; text-align: center; vertical-align: middle; }
清单 17-11 显示了我们如何将一个锚元素设计成一个标签。我们确保锚元素没有下划线(通过移除任何文本修饰),并将锚元素变成块元素而不是行内元素。
清单 17-11。标签标签的文本样式
.tabWrap .tabTriggerA { text-decoration: none; display: block; }
清单 17-12 显示了我们如何设计包含标签内容的列表项的样式。我们确保内容是一个块,设置一个细边框,并将position
属性的值设置为relative
(我们将li
元素的position
属性的值设置为absolute
)。我们还将overflow
属性的值设置为hidden
,以清除浮动。
清单 17-12。样式内容列表
.tabWrap .tabContentUL { display: block; border: 1px solid; position: relative; overflow: hidden; }
清单 17-13 显示了我们如何设计标签的内容。它包括一些值得注意的因素。首先,我们将opacity
属性的值设置为 0,这使得内容不可见。当访问者选择一个选项卡时,我们使用一个过渡效果将opacity
属性的值设置为 1,这使得内容可见。过渡效果提供了我们的动画。对于不支持不透明的浏览器,我们将visibility
属性的值设置为hidden
。当访问者点击标签时,我们将visibility
属性的值设置为visible
。为了定位内容,我们将position
属性的值设置为absolute
,并将top
和left
属性的值设置为 0。我们还指定了各种浏览器的过渡效果。最后,我们将z-index
属性的值设置为 1。在下一个规则中,我们将把 visible 选项卡的z-index
属性的值设置为 2,确保 visible 选项卡位于顶部。
清单 17-13。内容区样式
.tabWrap .tabContentLI { /* * We're starting with an opacity of 0 as the
* default state, and when the tab content is triggered * by the tab trigger, it will animate into 100% opacity. * We're also setting the visibility to hidden and changing * that to a visibility of visible on trigger selection * to accommodate browsers that don't handle opacity. */ -webkit-opacity: 0; -moz-opacity: 0; opacity: 0; padding: 20px; position: absolute; visibility: hidden; top: 0; left: 0; -webkit-transition: all 0.3s ease-out; -moz-transition: all 0.3s ease-out; -ms-transition: all 0.3s ease-out; -o-transition: all 0.3s ease-out; transition: all 0.3s ease-out; z-index: 1; }
清单 17-14 展示了我们如何设计活动(或当前)标签的样式。正如我们在前面的规则中所讨论的,非活动选项卡的opacity
属性的值是 0,非活动选项卡的visibility
属性的值是hidden
,非活动选项卡的z-index
属性的值是 1。要使选项卡可见,此规则会更改所有这些设置。它将opacity
属性的值设置为 1,将visibility
属性的值设置为visible
,并将z-index
的值设置为 2。我们设置了可见性和不透明性,以适应不处理不透明性的浏览器。这些浏览器失去了与opacity
属性相关的动画效果,但是由于可见性的设置,标签可以正常工作。结果是一个可见的选项卡。
清单 17-14。对活动标签进行样式化
.tabWrap .showTab { -webkit-opacity: 1; -moz-opacity: 1; opacity: 1; z-index: 2; visibility: visible; }
横向处理的样式
现在我们来看看应用于选项卡控件水平处理的样式。值tabTreatment1
表示水平处理。我们从设置选项卡控件的宽度开始。
清单 17-15 显示了我们如何为水平处理设置标签控件的宽度。
清单 17-15。设置横向处理的宽度
/* * Here we're making style choices for the tabTreatment1.
*/ .tabTreatment1 { width: 625px; }
清单 17-16 展示了我们如何在水平处理的选项卡控件中设计实际的选项卡(每个选项卡标签周围的结构)。在这条规则中,我们规定了非活动选项卡的外观。当一个选项卡被激活时,我们会覆盖其中的一些值。这里,我们指定圆角,并为每个选项卡创建渐变。我们还设置了边框和背景色。因为我们必须考虑许多不同的浏览器,这就变成了一个相对较长的列表。
清单 17-16。为水平处理设计标签
.tabTreatment1 .tabTriggerLI { -webkit-border-top-right-radius: 5px; -webkit-border-bottom-right-radius: 0; -webkit-border-bottom-left-radius: 0; -webkit-border-top-left-radius: 5px; -moz-border-radius-topright: 5px; -moz-border-radius-bottomright: 0; -moz-border-radius-bottomleft: 0; -moz-border-radius-topleft: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; border-top-left-radius: 5px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; background: blue; border: 1px solid #444; background: #fffbfb; background: blue; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmJmYiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjYl IiBzdG9wLWNvbG9yPSIjZmZmOWY5IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMjUlIiBzdG9wLWNv bG9yPSIjZmZlYmViIiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMjUlIiBzdG9wLWNvbG9yPSIjZmVl OGU4IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMzIlIiBzdG9wLWNvbG9yPSIjZmVlNGU0IiBzdG9w LW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMzMlIiBzdG9wLWNvbG9yPSIjZmVlMWUxIiBzdG9wLW9wYWNpdHk9 IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iNTAlIiBzdG9wLWNvbG9yPSIjZmVkNWQ1IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAg IDxzdG9wIG9mZnNldD0iNTElIiBzdG9wLWNvbG9yPSIjZmRjNmM3IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9m ZnNldD0iNjYlIiBzdG9wLWNvbG9yPSIjZmNiZWJmIiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iOTIl IiBzdG9wLWNvbG9yPSIjZmRhYWFiIiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMTAwJSIgc3RvcC1j b2xvcj0iI2ZkYTdhOCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgPC9saW5lYXJHcmFkaWVudD4KICA8cmVjdCB4PSIwIiB5PSIw IiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSJ1cmwoI2dyYWQtdWNnZy1nZW5lcmF0ZWQpIiAvPgo8L3N2Zz4=); background: -moz-linear-gradient(top, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%, #fee4e 4 32%, #fee1e1 33%, #fed5d5 50%, #fdc6c7 51%, #fcbebf 66%, #fdaaab 92%, #fda7a8 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fffbfb), color-
`stop(6%, #fff9f9), color-stop(25%, #ffebeb), color-stop(25%, #fee8e8), color-stop(32%, #fee4e4),
color-stop(33%, #fee1e1), color-stop(50%, #fed5d5), color-stop(51%, #fdc6c7), color-stop(66%,
fcbebf), color-stop(92%, #fdaaab), color-stop(100%, #fda7a8));
background: -webkit-linear-gradient(top, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%,
fee4e4 32%, #fee1e1 33%, #fed5d5 50%, #fdc6c7 51%, #fcbebf 66%, #fdaaab 92%, #fda7a8 100%);
background: -o-linear-gradient(top, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%, #fee4e4
32%, #fee1e1 33%, #fed5d5 50%, #fdc6c7 51%, #fcbebf 66%, #fdaaab 92%, #fda7a8 100%);
background: -ms-linear-gradient(top, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%, #fee4e4
32%, #fee1e1 33%, #fed5d5 50%, #fdc6c7 51%, #fcbebf 66%, #fdaaab 92%, #fda7a8 100%);
background: linear-gradient(to bottom, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%,
fee4e4 32%, #fee1e1 33%, #fed5d5 50%, #fdc6c7 51%, #fcbebf 66%, #fdaaab 92%, #fda7a8 100%);
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffbfb',
endColorstr='#fda7a8', GradientType=0);
}`
清单 17-17 展示了我们如何为水平处理设置活动标签的外观。正如在清单 17-16 的描述中所讨论的,我们覆盖了我们在.tabTreatment .tabTriggerLI
规则中设置的属性(在清单 17-16 中描述的规则)。首先,我们关闭底部边框,这样包含标签的区域看起来像是加入了包含内容的区域。然后我们把背景色改成稍微亮一点的颜色。最后,我们覆盖渐变设置来提供一个稍微亮一点的渐变。
清单 17-17。为水平处理设置活动标签样式
`.tabTreatment1 .activeTab {
/*
* We're setting the style for the selected tab
*/
border-bottom: none;
background: #fffbfb;
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw
Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl
c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk
aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag
b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmJmYiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjYl
IiBzdG9wLWNvbG9yPSIjZmZmOWY5IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMjUlIiBzdG9wLWNv
bG9yPSIjZmZlYmViIiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMjUlIiBzdG9wLWNvbG9yPSIjZmVl
OGU4IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMzIlIiBzdG9wLWNvbG9yPSIjZmVlNGU0IiBzdG9w
LW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iMzMlIiBzdG9wLWNvbG9yPSIjZmVlMWUxIiBzdG9wLW9wYWNpdHk9
IjEiLz4KICAgIDxzdG9wIG9mZnNldD0iNTAlIiBzdG9wLWNvbG9yPSIjZmVkNWQ1IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAg
IDxzdG9wIG9mZnNldD0iNTElIiBzdG9wLWNvbG9yPSIjZmNkNGQ0IiBzdG9wLW9wYWNpdHk9IjEiLz4KICAgIDxzdG9wIG9m
ZnNldD0iMTAwJSIgc3RvcC1jb2xvcj0iI2ZjZmNmYyIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgPC9saW5lYXJHcmFkaWVudD4K
ICA8cmVjdCB4PSIwIiB5PSIwIiB3aWR0aD0iMSIgaGVpZ2h0PSIxIiBmaWxsPSJ1cmwoI2dyYWQtdWNnZy1nZW5lcmF0ZWQp
IiAvPgo8L3N2Zz4=);
background: -moz-linear-gradient(top, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%, #fee4e
4 32%, #fee1e1 33%, #fed5d5 50%, #fcd4d4 51%, #fcfcfc 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #fffbfb), color-
stop(6%, #fff9f9), color-stop(25%, #ffebeb), color-stop(25%, #fee8e8), color-stop(32%, #fee4e4),
color-stop(33%, #fee1e1), color-stop(50%, #fed5d5), color-stop(51%, #fcd4d4), color-stop(100%,
fcfcfc));
background: -webkit-linear-gradient(top, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%, #fe
e4e4 32%, #fee1e1 33%, #fed5d5 50%, #fcd4d4 51%, #fcfcfc 100%);
background: -o-linear-gradient(top, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%, #fee4e4
32%, #fee1e1 33%, #fed5d5 50%, #fcd4d4 51%, #fcfcfc 100%);
background: -ms-linear-gradient(top, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%, #fee4e4
32%, #fee1e1 33%, #fed5d5 50%, #fcd4d4 51%, #fcfcfc 100%);
background: linear-gradient(to bottom, #fffbfb 0%, #fff9f9 6%, #ffebeb 25%, #fee8e8 25%,
fee4e4 32%, #fee1e1 33%, #fed5d5 50%, #fcd4d4 51%, #fcfcfc 100%);
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#fffbfb',
endColorstr='#fcfcfc', GradientType=0);
}`
清单 17-18 显示了我们如何设计组成标签的锚标签的样式。我们所做的就是将颜色设置为深灰色(我们发现灰色比纯黑色更有吸引力)。
清单 17-18。设置水平处理的标签颜色
.tabTreatment1 .tabTriggerA { color: #222; }
清单 17-19 显示了我们如何设计一个标签的内容区域。首先,我们将边框设置为灰色。然而,因为我们不想在顶部设置边框,所以我们关闭了顶部边框。之后,我们创建圆角,设置内容区域周围的阴影,并设置当访问者改变标签时产生动画的过渡。因为我们必须让这些设置在许多不同的浏览器上工作,所以这些任务的代码有一个很大的清单。
清单 17-19。对内容区进行横向处理
.tabTreatment1 .tabContentUL { border: 1px solid #444; border-top: none; -webkit-border-top-right-radius: 0; -webkit-border-bottom-right-radius: 3px; -webkit-border-bottom-left-radius: 3px; -webkit-border-top-left-radius: 0; -moz-border-radius-topright: 0; -moz-border-radius-bottomright: 3px; -moz-border-radius-bottomleft: 3px; -moz-border-radius-topleft: 0; border-top-right-radius: 0; border-bottom-right-radius: 3px; border-bottom-left-radius: 3px; border-top-left-radius: 0; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; -webkit-box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.4); -moz-box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.4); box-shadow: 0 3px 3px 0 rgba(0, 0, 0, 0.4); /* * To achieve our animation for the height * of the content tab as it's changing content,
* we set the transition property below. This * tells it to transition on all available * transition properties when there's a change. */ -webkit-transition: all 0.3s ease-out; -moz-transition: all 0.3s ease-out; -ms-transition: all 0.3s ease-out; -o-transition: all 0.3s ease-out; transition: all 0.3s ease-out; }
我们将从清单 17-20 的开始,对接下来的几条规则进行分组,因为它们都适用于 Internet Explorer。IE6 和 IE7 不让我们用display: table-cell
和display: table properties
。即使是理解display:table types
的 IE9,也有一个复杂渐变和所有事物边界的问题。因此,我们必须使用另一种方法:浮动列表项。我们还将最后一个列表项上的overflow
属性的值设置为hidden.
,这种技术使得选项卡占据了选项卡控件的整个宽度。
清单 17-20。用于横向处理的 Internet Explorer 样式
/* * Because even IE9 still doesn't always play well, we need * to take a different approach by not using the display:table and * display:table-cell properties. Instead, we go with a more * traditional approach: floating the LIs. We're also doing a bit * of trickery by setting the last LI to overflow:hidden and taking * away the float. This trick makes the tab take up the remaining * space so that our tabs will take up the full width of the tab * control. */ .ie .tabTreatment1 .tabTriggerLI { display: block; float: left; margin: 0; position: relative; } .ie .tabTreatment1 .tabTriggerUL { display: block; overflow: hidden; } .ie .tabTreatment1 .tabLast { float: none; overflow: hidden; } .ie .tabTreatment1 .tabTriggerA { position: relative; top: 50%; display: block; }
清单 17-21 也捆绑了一些规则(在本例中是两个)。如果我们让 IE9 的filter
属性保持活动状态,我们将得到一个简单的两级渐变。SVG 让我们有了多级渐变。因此,我们必须删除过滤器在 IE9 中获得更好的梯度。我们需要两个规则,这样我们可以为所有选项卡和活动选项卡覆盖filter
属性。
清单 17-21。将 IE9 水平处理的滤镜设置为无
/* * We need to disable the filter property * for IE9 so we can use our SVG alternative. */ .ie9 .tabTreatment1 .tabTriggerLI { filter: none; } .ie9 .tabTreatment1 .activeTab { filter: none; }
垂直处理的样式
现在我们来看看应用于选项卡控件的垂直处理的样式。数值tabTreatment2
表示垂直处理。我们首先设置一大组适用于整个处理的属性。
清单 17-22 展示了我们如何设置一些适用于整个处理的属性。我们从设置宽度开始。然后我们设置overflow
属性的值来清除浮动。然后我们设置填充和背景颜色。接下来,我们设置渐变和创建圆角。和往常一样,对于这些属性,我们必须为每个属性设置许多行,以便在尽可能多的浏览器上获得我们想要的外观。
清单 17-22。设置整个垂直处理的属性
.tabTreatment2 { width: 605px; overflow: hidden; padding: 10px; background: #45484d; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzQ1NDg0ZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEw MCUiIHN0b3AtY29sb3I9IiMwMDAwMDAiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3Qg eD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9z dmc+); background: -moz-linear-gradient(top, #45484d 0%, #000000 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #45484d), color- stop(100%, #000000)); background: -webkit-linear-gradient(top, #45484d 0%, #000000 100%); background: -o-linear-gradient(top, #45484d 0%, #000000 100%); background: -ms-linear-gradient(top, #45484d 0%, #000000 100%); background: linear-gradient(to bottom, #45484d 0%, #000000 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#45484d', endColorstr='#00000 0', GradientType=0);
border: 1px solid; -webkit-border-radius: 3px; -moz-border-radius: 3px; border-radius: 3px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; }
清单 17-23 展示了我们如何将包含标签的列表转换成一个表格进行垂直处理。因为我们将宽度设置为 100 %,所以选项卡控件现在填充了它的可用空间。与适用于所有处理的规则不同(参见清单 17-9 ,该规则将float
属性的值设置为left
,宽度设置为 150 像素,右边距设置为 10 像素。
清单 17-23。将列表变成表格进行垂直处理
.tabTreatment2 .tabTriggerUL { display: block; width: 150px; float: left; height: 100%; margin-right: 10px; }
清单 17-24 展示了我们如何在水平处理的选项卡控件中设计实际的选项卡(每个选项卡标签周围的结构)。我们使用这个规则来规定非活动选项卡的外观。当一个选项卡被激活时,我们会覆盖其中的一些值。在这里,我们指定填充、对齐、边距和阴影。我们还确保选项卡显示为一个块,并且相对定位。然后我们设置渐变和背景色。
清单 17-24。为垂直处理设计标签
.tabTreatment2 .tabTriggerLI { padding: 10px; text-align: center; position: relative; display: block; margin: 4px 0; -webkit-box-shadow: 0 0 3px rgba(255, 255, 255, 0.7); -moz-box-shadow: 0 0 3px rgba(255, 255, 255, 0.7); box-shadow: 0 0 3px rgba(255, 255, 255, 0.7); background: #7d7e7d; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzdkN2U3ZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEw MCUiIHN0b3AtY29sb3I9IiMwZTBlMGUiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3Qg eD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9z dmc+); background: -moz-linear-gradient(top, #7d7e7d 0%, #0e0e0e 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #7d7e7d), color-
stop(100%, #0e0e0e)); background: -webkit-linear-gradient(top, #7d7e7d 0%, #0e0e0e 100%); background: -o-linear-gradient(top, #7d7e7d 0%, #0e0e0e 100%); background: -ms-linear-gradient(top, #7d7e7d 0%, #0e0e0e 100%); background: linear-gradient(to bottom, #7d7e7d 0%, #0e0e0e 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#7d7e7d', endColorstr='#0e0e0e', GradientType=0); }
清单 17-25 显示了我们如何为标签控件的垂直处理设计标签样式。首先,我们移除文本装饰,因为我们不希望文本加下划线。然后,我们确保文本是一个块,并设置颜色为白色。然后,我们确保文本相对定位,并将顶部距离设置为 50%。
清单 17-25。为垂直处理设计标签样式
.tabTreatment2 .tabTriggerA { text-decoration: none; display: block; color: white; position: relative; top: 50%; }
清单 17-26 展示了我们如何为垂直处理设计内容区域的样式。首先,我们确保内容显示为一个块。然后,我们将边框设置为中灰色。接下来,我们将定位设置为相对的。然后我们防止内容溢出内容区,因为那样看起来很糟糕。最后,我们设置当访问者改变标签时提供动画的过渡效果。
清单 17-26。为垂直处理的内容区域设置样式
.tabTreatment2 .tabContentUL { display: block; border: 1px solid #999; position: relative; overflow: hidden; /* * To achieve our animation for the height * of the content tab as it's changing content, * we set the transition property below. This * tells it to transition on all available * transition properties when there's a change. */ -webkit-transition: all 0.3s ease-out; -moz-transition: all 0.3s ease-out; -ms-transition: all 0.3s ease-out; -o-transition: all 0.3s ease-out; transition: all 0.3s ease-out; }
清单 17-27 展示了我们如何为垂直处理设计标签的内容区域。我们将各种opacity
属性的值设置为 0,将visibility
属性的值设置为 0,并将z-index
设置为 1。这些设置使内容区域不可见。我们将为活动选项卡覆盖这些值。我们还设置了阴影、背景渐变和背景颜色来控制内容区域的外观。在这个过程中,我们设置了当访问者改变标签时提供动画的转换值。因为我们必须让这些设置在许多不同的浏览器上工作,所以这些任务的代码有一个很大的清单。
清单 17-27。为垂直处理的内容区域设置样式
.tabTreatment2 .tabContentLI { /* * We're starting with an opacity of 0 as the * default state, and when the tab content is triggered * by the tab trigger, it will animate into 100% opacity. * We're also setting the visibility to hidden and changing * that to a visibility of visible on trigger selection * to accommodate browsers that don't handle opacity. */ -webkit-opacity: 0; -moz-opacity: 0; opacity: 0; padding: 20px; -webkit-box-shadow: inset 2px 2px 2px rgba(0, 0, 0, 0.6); -moz-box-shadow: inset 2px 2px 2px rgba(0, 0, 0, 0.6); box-shadow: inset 2px 2px 2px rgba(0, 0, 0, 0.6); position: absolute; visibility: hidden; top: 0; left: 0; -webkit-transition: all 0.3s ease-out; -moz-transition: all 0.3s ease-out; -ms-transition: all 0.3s ease-out; -o-transition: all 0.3s ease-out; transition: all 0.3s ease-out; z-index: 1; background: #ffffff; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2ZmZmZmZiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjQ3 JSIgc3RvcC1jb2xvcj0iI2Y2ZjZmNiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3At Y29sb3I9IiNlZGVkZWQiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0i MCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #ffffff), color-stop(47%, #f6f6f6), color- stop(100%, #ededed)); background: -webkit-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%); background: -o-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%); background: -ms-linear-gradient(top, #ffffff 0%, #f6f6f6 47%, #ededed 100%); background: linear-gradient(to bottom, #ffffff 0%, #f6f6f6 47%, #ededed 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#ffffff', endColorstr='#ededed', GradientType=0);
}
清单 17-28 展示了我们如何为垂直处理制作一个可见的标签。为此,我们将属性opacity
的值重写为 1,属性visibility
的值重写为visible
,属性z-index
的值重写为 2。该设置集合使选项卡可见。
清单 17-28。使垂直治疗的标签可见
.tabTreatment2 .showTab { -webkit-opacity: 1; -moz-opacity: 1; opacity: 1; z-index: 2; visibility: visible; }
清单 17-29 展示了我们如何为垂直处理设置活动标签的样式。在这种情况下,我们只需要设置背景颜色和背景渐变。和以往一样,当我们需要渐变时,需要相当多的代码,不仅因为我们使用 SVG,还因为我们必须为所有可能的浏览器设置它。
清单 17-29。设计垂直处理的活动标签
.tabTreatment2 .activeTab { background: #444444; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iIzQ0NDQ0NCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEw MCUiIHN0b3AtY29sb3I9IiM5ZTllOWUiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3Qg eD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9z dmc+); background: -moz-linear-gradient(top, #444444 0%, #9e9e9e 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #444444), color-stop(100%, #9e9e9e)); background: -webkit-linear-gradient(top, #444444 0%, #9e9e9e 100%); background: -o-linear-gradient(top, #444444 0%, #9e9e9e 100%); background: -ms-linear-gradient(top, #444444 0%, #9e9e9e 100%); background: linear-gradient(to bottom, #444444 0%, #9e9e9e 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#444444', endColorstr='#9e9e9e', GradientType=0); }
清单 17-30 显示了我们如何为垂直处理关闭 IE9 的滤波器。因为 IE 9 会呈现一个简单得多的两级渐变,而我们想要更好的多级 SVG 渐变,所以我们覆盖了垂直处理的属性filter
的值,这样 IE9 的值就是none
。
清单 17-30。关闭 IE9 的滤镜。
.ie9 .tabTreatment2 .activeTab { filter: none; }
总结
本章展示了如何开发和使用选项卡控件。和我们所有的控件一样,我们使用一个单独的 HTML 块,由作为控件核心的函数从数据中生成。从那块积木上,我们制作了两种不同的处理方式,一种是水平的浅色处理,一种是垂直的深色处理。我们还采用了我们的标准范例,尽可能使用 CSS3,但是在 JavaScript 中为不能使用 CSS3 的浏览器提供了一个后备。因此,我们接受渐进式设计的原则,给每个访问者提供访问者浏览器所能提供的最佳体验。如前所述,我们不试图为每个访问者提供相同的体验。我们认为这是一个错误的目标,因为不同的浏览器不可能有真正相同的体验。相反,我们的目标是在任何浏览器上都有良好的体验。
对于这个控件,我们还创建了一个 jQuery 插件,让我们为特定的选项卡创建一个地址。这样,返回或刷新页面的访问者会得到他们刚才所在的标签页。在一组选项卡上设置唯一标识符的能力还允许一个页面拥有多组选项卡。该地址(由id
属性启用)还允许我们创建到特定选项卡的链接。为了增加一点视觉趣味,我们还制作了从一个选项卡到另一个选项卡的动画。所有这些工作的最终结果是一个可重用的选项卡控件,它看起来很好,并且比我们在各种网站上找到的大多数选项卡排列具有更好的功能。
十八、表单控件
对于许多网站来说,表单是必不可少的,对于电子商务网站来说更是如此。如果我们不能收集访问者的信息,我们就不能做生意,而表单就是收集信息的机制。因此,如果没有关于表单的章节,我们真的无法完成这本书。
为了构建表单控件,我们必须构建两个控件。表单有内容,所以我们为表单的内容构建了一个控件:fieldset 控件。一个字段集包含input
元素,所以我们还需要一个控件来构建input
元素。
注我们没有做实际的表单控件。为了用我们的控件创建一个表单,我们创建了一个
form
元素并使用字段集控件作为子控件。我们在本章后面的“创建表单”一节中提供了一个例子。
像往常一样,我们为一个控件创建了多种处理方式——在表单控件的情况下是暗处理和亮处理。深色处理有一个对角线图案,看起来几乎像灯芯绒(这是周杰伦看到它时说出的第一个词)。我们将在本章后面讨论 CSS 规则时展示如何创建这种效果。
在该表单中,有两组字段。正如我们在本章开始时提到的,我们用 fieldset 控件来构建表单的内容。接下来,我们将展示如何用 fieldset 控件为表单构建字段。因为我们需要字段集中所有常见的input
元素,我们还将展示如何用输入控件构建它们。
表单控件使用标准的form
元素,只有一个例外。提交按钮是我们按钮控件的一个实例,在第十三章中有描述。稍后,当我们看到表单控件背后的代码时,我们将展示如何将按钮控件插入表单控件。
我们的表单控件以多种不同的方式提供了良好的性能。第一,我们尽可能的依赖 CSS。正如我们在前面的章节中提到的,浏览器使用 CSS 规则比使用 JavaScript 或任何其他机制更快地呈现内容(事实上,我们在表单控件中根本不使用 JavaScript)。第二,我们在 CSS 规则中使用类名,并避免后代选择器,后者的性能远不如前者。同样,这是我们一贯使用的模式;我们认为随着开发者意识到它的好处,它将变得更加普遍。第三也是最后一点,构建控件来呈现我们的表单在很大程度上加快了开发人员(包括我们自己)的速度。开发人员可以更容易地调用一些函数来创建表单,而不是自己创建表单的所有元素。
最后一项还有另一个好处:封装。如果表单有问题,我们知道应该查看表单控件的代码,而不是查看许多单个的表单。这项技术大大加快了调试速度。当已知问题在一个小空间中时,调试也不那么令人沮丧。减少挫折感实际上并不能提高开发人员的绩效,但无论如何这很好。我们欣赏任何让我们的工作不那么令人沮丧,从而更有趣的事情。
展示往往比讲述更容易、更有效,所以让我们看看这两种处理在渲染时是什么样子的。这样,你就会明白我们所说的“暗”和“亮”是什么意思,也会明白字段集控件、输入控件和按钮控件是如何适应表单的。
如果你想在网上看到表单控件——截图和真实的东西是不一样的——你可以在我们的示例网站上找到它:[
clikz.us/BookExamples/forms.php](http://clikz.us/BookExamples/forms.php)
(我们之前提到过,这本书是关于网站的前端——HTML 和 CSS——所以我们网站上的表单不与任何东西挂钩)。
图 18-1 显示了表单控件的暗处理。
图 18-1。表单控件的暗处理
图 18-2 显示了表单控件的光处理。
图 18-2。表单控件的光处理
现在您已经看到了它们是如何在浏览器中呈现的,让我们继续关注呈现的表单处理背后的 HTML。
HTML
和我们的其他控件一样,这个控件不直接包含 HTML。相反,当页面加载时,PHP 函数会生成 HTML。尽管如此,我们认为看到控件创建的 HTML 提供了样式(CSS)信息的上下文。除了指定使用哪种处理作为类名之外,HTML 不会从深色处理变为浅色处理。CSS 规则(我们很快会谈到)决定了处理之间的所有差异。
HTML 由两个fieldset
元素组成,每个元素包含一个图例和一些input
元素。字段集是我们的字段集控件的实例,而input
元素是我们的输入控件的实例。在第二个字段集的底部,我们放入一个提交按钮,这也是按钮控件的一个实例。
清单 18-1 展示了图 18-1 和图 18-2 中显示的表单控件的 HTML。在这里,它通过fieldsetTreatment1
类名指定了暗处理。
清单 18-1。表单控件两种处理方式背后的 HTML】
`
`由于字段集包含所有其他内容,我们将从字段集控件开始。
字段集控件
在我们的工作和本书中,我们大量使用了fieldset
元素。因为fieldset
元素可以包含legend
元素,所以我们用它们来命名表单中的部分。除了fieldset
和legend
元素,我们还使用了整个网络社区长期使用的div
和input
元素。
与本书中的其他示例控件一样,实际的控件是一个 PHP 函数。该函数有三个参数。表 18-1 描述了它们。
为了将模型中的数据转换成具有正确属性和内容值的 HTML 元素,fieldset 控件首先从模型中获取数据。数据以字符串的形式被解码成 JSON 对象。然后,该函数从数据对象获取字段,并将它们放入一个单独的变量中。接下来,函数创建一个输出变量,它保存函数处理的结果;本质上,输出变量保存一个稳定增长的字符串,该字符串包含 HTML 元素,数据插入到正确的位置。输出变量以fieldset
元素的开始标签及其class
属性开始。然后,该函数确定图例值是否存在,如果存在,则向输出变量的字符串添加一个legend
元素。然后,该函数使用一个for
循环来读取每个字段。for
循环有几个 if 语句来识别出现了哪种字段并相应地处理它。对于每个字段,该函数通过调用输入控件(有几个变体)并将调用结果放入输出,将更多的 HTML 元素附加到输出变量的字符串中。接下来,该函数关闭fieldset
元素。最后,它确定调用是希望将输出字符串发送(回显)到浏览器还是返回到某个其他函数,并执行相应的操作。
我们将在本章后面介绍输入控制和数据的细节。现在,请接受数据可能包含一个图例并且必须包含一些字段,并且对输入控件的每个调用都返回一个在正确位置包含数据的input
元素。
注意虽然清单 18-2 看起来很大,但是一个包含所有子元素和数据的表单会更大。控件的一个好处(也是使用它们的一个主要动机)是,一旦你有了控件,使用它比从头开始创建等效的功能需要更少的代码。
清单 18-2。字段集控件
`function fieldSetControl($model, $treatment, $echo) {
\(obj = json_decode(\)model);
$fields = \(obj -> fields;
\)output = '
if ($echo != "return") {
echo $output;
} else {
return $output;
}
};`
现在你已经看到了字段集控件,让我们继续看输入控件,字段集控件广泛使用它。
输入控件
正如我们在描述字段集控件时所观察到的,输入控件返回一个格式正确的input
元素(或其他元素;请参阅本节后面的内容)添加到字段集控件(或其他任何名称)。它还可以将其输出直接发送到浏览器,这样就可以在没有 fieldset 控件的情况下使用它。毕竟,fieldset
元素是可选的,并不总是有用的(比如当一个表单只有一个字段块时)。同样,输入控件是一个 PHP 函数。正如我们已经提到的所有其他控件一样,字段集控件和输入控件都可以很容易地用其他语言编写。我们必须选择一种语言,PHP 赢了,主要是因为我们认为它容易阅读。
输入控件实际上是几个相关的控件;也就是说,输入控件由许多函数组成,每个函数都返回一个填充的元素。让我们从名为inputControl
的函数开始,它创建了一个input
元素。
注意并非所有输入控件中的函数都创建
input
元素。该控件的其他功能创建其他元素;它们的存在是为了让我们可以在表单中注入其他元素(特别是按钮和其他元素)。
inputControl
函数接受许多参数,并将它们转换成适当填充的输入控件。表 18-2 描述了inputControl
函数的参数。
输入控件通过创建一个输出变量来工作(就像我们的大多数控件一样),并在该变量中构建一个字符串,该字符串成为一个正确填充的元素。在大多数情况下,它获取其参数的值,并将它们插入到结果input
元素的相应属性中。$required
参数需要一点逻辑来处理,因为它在input
元素中产生两个可选字符串:一个包含星号的 span 和元素属性中的单词“required”。一旦输入控件将所有正确的部分放在正确的位置,它就可以将生成的input
元素发送给浏览器,或者将包含input
元素的字符串返回给调用者(在清单 18-2 中,这是我们的字段集控件)。
清单 18-3。输入控制功能
function inputControl($id, $labelText, $name, $value, $type, $placeholder, $class, $required, $optional, $echo) { $output = '<div class="formElement inputControl
' . $class . '">'; $output .= '<label class="structure" for="' . $id . '">' . $labelText; $output .= ($required == TRUE ? '<span class="required">*</span>' : ''); $output .= '</label>'; $output .= '<input type="' . $type . '" name="' . $name . '" value="' . $value . '" id="' . $id . '" placeholder="' . $placeholder . '"'; $output .= ($required == TRUE ? ' required = "required"' : ''); $output .= ' ' . $optional . '/>'; $output .= '</div>'; if ($echo != "return") { echo $output; } else { return $output; } }
inputList
函数创建一个包含一组相关input
元素的div
,这些元素构成了一组复选框或单选按钮。表 18-3 描述了inputList
功能的参数。
与控件中的其他函数一样,inputList
函数构建一个由填充的 HTML 元素和属性组成的字符串。首先,该函数写入包含这个input
元素列表的div
元素的开始标签。然后该函数写入label
元素。为了创建input
元素,该函数使用一个for
循环来写入与$model
参数中的值一样多的input
元素(以及相应的label
元素)。该函数将input
元素写入输出变量后,关闭div
元素。最后,该函数要么将div
元素及其所有子元素发送到浏览器,要么将包含所有这些元素的字符串发送回调用者。
清单 18-4。输入列表功能
function inputList($model, $labelText, $name, $type, $class, $echo) { $obj = $model; $output = '<div class="formElement inputList' . $class . '">'; $output .= '<label class="structure">' . $labelText . '</label>'; for ($i = 0; $i < sizeof($obj); $i++) { $unique = uniqid($obj[$i] -> value . '_'); $output .= '<input id="' . $unique . '" value="' . $obj[$i] -> value . '" name="' . $name . '" type="' . $type . '">'; $output .= '<label class="textLabel" for="' . $unique . '">' . $obj[$i] -> labeltext . '</ label>'; } $output .= '</div>'; if ($echo != "return") { echo $output; } else { return $output; } }
函数将任意的 HTML 元素插入到一个表单中。表 18-4 描述了inputHTML
函数的参数。
最简单的输入控制函数,inputHTML
函数只是创建一个div
,设置在$class
参数中指定的任何类,并将$html
参数的内容放入div
。然后,如果$echo
参数是return
,它将包含插入的 HTML 的div
发送给调用者,或者,如果$echo
参数是其他任何参数,它将输出div
及其内容作为字符串发送给浏览器。
清单 18-5。input html 函数
function inputHTML($html, $class, $echo) { $output = '<div class="formElement ' . $class . '">'; $output .= $html; $output .= '</div>'; if ($echo != "return") { echo $output; } else {
return $output; } }
函数的作用是:在一个表单中插入一个或多个按钮控件。这是另一个在控件中使用控件的例子,就像我们经常做的那样。表 18-5 描述了inputButtons
函数的参数。
按照我们通常对control
函数的范例,inputButtons
函数首先创建一个输出变量,并将div
元素的开始标记(包括类信息)写入该输出变量。然后,它使用一个for
循环遍历数据,并为输入数据中指定的每个按钮创建一个按钮控件(参见第十三章)。然后关闭包含按钮控件的div
元素。最后,如果$echo
参数是return
,它将输出发送给调用者,或者,如果$echo
参数是其他任何参数,则将输出作为字符串发送给浏览器。
清单 18-6。输入按钮功能
function inputButtons($model, $class, $echo) { $output = '<div class="formElement buttonWrap ' . $class . '">'; for ($j = 0; $j < sizeof($model); $j++) { $obj = $model[$j]; $output .= button($obj -> text, $obj -> href, $obj -> id, $obj -> text, $obj -> type, NULL, NULL, NULL, NULL, "return"); } $output .= '</div>'; if ($echo != "return") { echo $output; } else { return $output; } }
函数在一个表单中创建一个互斥选项列表。为此,它创建了一个包含一个select
元素的div
。select
元素包含与数据中存在的option
元素一样多的元素。表 18-6 描述了inputSelect
功能的参数。
inputSelect
函数插入一个div
元素的开始标签,并在div
元素上设置类。然后该函数添加标签和select
元素的开始标记。接下来,该函数使用一个for
循环遍历$model
参数中的数据,并为数据中的每个值创建一个option
元素。接下来,该函数关闭select
和div
元素。最后,如果$echo
参数是return
,它将结果 div 元素和它包含的列表发送给调用者,或者,如果$echo
参数是其他任何东西,则将div
元素和它包含的列表作为字符串发送给浏览器。
清单 18-7。输入选择功能
function inputSelect($model, $id, $labelText, $class, $echo) { $output = '<div class="formElement selectWrap ' . $class . '">'; $output .= '<label class="structure">' . $labelText . '</label>'; $output .= '<select id="' . $id . '">'; for ($i = 0; $i < sizeof($model); $i++) { $output .= '<option value="' . $model[$i] -> value . '">' . $model[$i] -> label . '</option>'; } $output .= '</select>'; $output .= '</div>'; if ($echo != "return") { echo $output; } else { return $output; } }
数据对象
因为表单中有两个字段集控件,所以我们需要两组数据,一组用于联系信息,一组用于地址信息。在实际应用中,我们从数据库中获取这些数据。然而,由于我们坚持前端,我们只使用静态数据。每个数据块由一个包含 JSON 对象的 PHP 变量组成。JSON 对象包含三个值。表 18-7 描述了提供给字段集控件的 JSON 对象中的值。
清单 18-8 显示了为contact
块提供数据的 JSON 对象的内容。同样,我们通常会从一个从数据库获取值的对象中获取数据,并构建一个 JSON 对象,然后将该对象传递给我们的 fieldset 控件。
清单 18-8。端子块的数据
$contactInfo = '{ "showLegend" : true, "legend" : "Contact Info", "fields" : [ { "formElement" : "inputControl", "id": "id1", "labelName" : "First Name", "name" : "first_name", "value" : "", "type" : "text", "placeholder" : "", "_class" : "", "required" : true, "optional" : "", "_echo" : "return" }, { "formElement" : "inputControl",
"id": "id2", "labelName" : "Last Name", "name" : "last_name", "value" : "", "type" : "text", "placeholder" : "", "_class" : "", "required" : true, "optional" : "", "_echo" : "return" }, { "formElement" : "inputControl", "id": "id3", "labelName" : "Email", "name" : "email", "value" : "", "type" : "email", "placeholder" : "name@domain.com", "_class" : "", "required" : true, "optional" : "", "_echo" : "return" }, { "formElement" : "inputControl", "id": "id4", "labelName" : "Phone", "name" : "phone", "value" : "", "type" : "tel", "placeholder" : "555-555-1212", "_class" : "", "required" : false, "optional" : "", "_echo" : "return" }, { "formElement" : "inputList", "model": [{"value" : "men", "labeltext" : "Male"},{"value" : "women", "labeltext" : "Female"}], "labelName" : "Gender", "name" : "gender", "type" : "checkbox", "_class" : "", "_echo" : "return" }, { "formElement" : "html", "html" : "<div class=textMessage>We will never share your information.</div>", "_class" : "", "_echo": "return" } ] }';
清单 18-9 显示了为address
块提供数据的 JSON 对象的内容。正如我们之前提到的,我们通常从一个对象获取数据,该对象从数据库获取值并构建一个 JSON 对象,然后将该对象传递给我们的 fieldset 控件。
清单 18-9。地址块的数据
$addressInfo = '{ "showLegend" : true,
"legend" : "Mailing Address", "fields" : [ { "formElement" : "inputControl", "id": "aid1", "labelName" : "Address", "name" : "address", "value" : "", "type" : "text", "placeholder" : "", "_class" : "", "required" : false, "optional" : "", "_echo" : "return" }, { "formElement" : "inputControl", "id": "aid2", "labelName" : "City", "name" : "city", "value" : "", "type" : "text", "placeholder" : "", "_class" : "", "required" : false, "optional" : "", "_echo" : "return" }, { "formElement" : "inputSelect", "id": "aid3", "labelName" : "State", "model" : [{"label": "Alabama", "value" : "AL"},{"label": "Alaska", "value" : "AK"},{"label": "Arizona", "value" : "AZ"},{"label": "Arkansas", "value" : "AR"}], "_class" : "", "required" : false, "optional" : "", "_echo" : "return"
}, { "formElement" : "inputControl", "id": "aid4", "labelName" : "Zip Code", "name" : "zipcode", "value" : "", "type" : "number", "placeholder" : "", "_class" : "", "required" : false, "optional" : "", "_echo" : "return" }, { "formElement": "buttons", "buttons" : [ { "text": "Submit", "href": "#", "id" : "bid1", "type" : "primaryGlass", "_echo" : "return" } ] } ] }';
创建表单
正如本章开始时提到的,我们并没有制作一个实际的表单控件。相反,我们制作了提供表单内容的控件。所以要制作一个表单,我们创建一个普通的form
元素,然后使用我们的控件。我们在本章中使用的例子在form
元素中有两个字段集控件。我们也可以在form
元素中使用输入控件。但是,在示例中我们不需要这样做。相反,输入控件位于字段集控件内。我们的示例还使用了两种形式,一种用于暗处理,一种用于亮处理。清单 18-10 展示了我们如何创建用作样本的表单。
清单 18-10。创建我们的样本表单
`
Form Treatment 1
Form Treatment 2
CSS
正如我们所有的控件一样,处理之间的差异完全出现在驱动不同处理的 CSS 规则中。除了指定处理的名称(fieldsetTreatment1
为暗处理,fieldsetTreatment2
为亮处理),处理之间的函数和数据没有任何变化。像往常一样,我们将单独评论大多数 CSS 规则,尽管我们可能会把一些更明显的规则放在一起。
清单 18-11 展示了我们如何将fieldset
元素的边距和填充值设置为零(0 ),这样这些设置就可以应用到字段集控件中的所有元素。
清单 18-11。移除边距和填充
.fieldset { margin: 0; padding: 0; }
清单 18-12 展示了我们如何在标签后插入冒号。
清单 18-12。在标签后插入冒号
.fieldset label.structure:after { content: ":"; }
清单 18-13 展示了我们如何设置星号,我们用它来表示一个项目需要红色,并设置一点填充来防止星号进入标签的文本。
清单 18-13。样式化所需的星号
.required { color: red; padding: 2px; }
清单 18-14 展示了我们如何为暗处理设计表单的样式。从本质上讲,这条规则设计了保存表单中所有内容的框的样式。因为formTreatment1
有一个深色的背景图像,我们将文本设置为白色。然后我们设置文本阴影、填充、边框半径(创建圆角)、框阴影和边框。在这个过程中,我们将背景剪辑(CSS 3 的一个属性)设置为padding-box
,这样可以将背景从边框中分离出来。我们还确保表单显示为一个块,并将宽度设置为 400 像素。我们还将position
属性的值设置为relative
,因为我们稍后将在表单中设置带有absolute
定位的元素。最后,我们将背景图像设置为小的(边长 7 像素)图像,这给了我们“灯芯绒”的效果。
清单 18-14。为暗处理设计表单元素
.formTreatment1 { color: white; text-shadow: 0 0 2px black; padding: 20px; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; -webkit-box-shadow: inset 1px 1px 1px #777777; -moz-box-shadow: inset 1px 1px 1px #777777;
box-shadow: inset 1px 1px 1px #777777; border: 1px solid rgba(0, 0, 0, 0.2); background: #CCC; display: block; position: relative; width: 400px; background: #ffffff url(/img/background_stripped.png); }
清单 18-15 显示了我们如何在表单周围添加一个额外的边框来提供更多的视觉吸引力。为此,我们使用before
伪选择器,将position
属性的值设置为absolute
,然后将top
属性和left
属性的值都设置为-10 像素。然后将填充和边框设置为 10 像素,并创建一个 10 像素的阴影,这样我们就得到一个 10 像素宽的表单包装。为了使包装器可见,我们还将其z-index
值设置为 1。
清单 18-15。在表单周围添加一个 10 像素的包装
.formTreatment1:before { content: ""; width: 100%; height: 100%; display: block; z-index: -1; position: absolute; padding: 10px; background: #CCC; left: -10px; top: -10px; -webkit-border-radius: 10px; -moz-border-radius: 10px; border-radius: 10px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; -webkit-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4), 2px 2px 2px rgba(0, 0, 0, 0.4); -moz-box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4), 2px 2px 2px rgba(0, 0, 0, 0.4); box-shadow: inset 0px 0px 4px rgba(0, 0, 0, 0.4), inset 0 10px 2px rgba(255, 255, 255, 0.4), 2px 2px 2px rgba(0, 0, 0, 0.4); }
清单 18-16 显示了我们如何禁用 IE9 之前的 IE 浏览器版本的包装器。旧版本的浏览器不能正确呈现包装器;宁可什么都没有,也不要一团糟,我们关闭了这些版本的包装器。
清单 18-16。关闭 IE9 之前版本的 IE 浏览器的包装器。
.lt-ie9 .formTreatment1:before { display: none; }
清单 18-17 展示了我们如何为暗处理(由fieldsetTreatment1
标识)设计一个字段集控件的样式。由于我们将它用作其他元素(主要是用作按钮的input
元素和anchor
元素)周围的外部元素,所以我们希望padding
值为 0,希望position
值为relative
。我们还将border
值设置为none
,并添加 10 像素的底部边距(以使字段集远离任何后续元素)。
清单 18-17。造型暗场处理
.fieldsetTreatment1 { padding: 0px; position: relative; border: none; margin-bottom: 10px; }
清单 18-18 展示了我们如何在一个字段集中设置legend
元素的样式。我们将左边的padding
值设置为 10 像素,让文本稍微缩进一些。我们还设置顶部和底部的padding
值为 5 像素,以防止文本进入其他元素。我们可以忽略右边的padding
值,所以我们将其设置为 0。我们还在文本上创建一条白色实线,在字段集控件和它前面的任何控件之间提供一个边界。(虽然线条是在应用于legend
元素的样式中定义的,但它具有为字段集控件创建顶部边界的效果,因为legend
元素是字段集控件中的第一个元素。)因为我们不希望legend
元素是块以外的任何东西,所以我们将display
属性的值设置为block
并添加了!important
说明符。我们还将字体粗细设置为粗体(人们看不到的图例有什么用?)并将底部边距设置为 20 像素,以避免字段拥挤。我们喜欢白色空间(或灯芯绒空间,在这种情况下)。我们还将position
的值设置为relative
;我们在下一个规则的描述中解释了为什么我们需要这个设置。
清单 18-18。造型图例元素
.fieldsetTreatment1 legend { padding: 5px 0 5px 10px; font-weight: bold; border-top: 2px solid white; display: block !important; width: 100%; margin-bottom: 20px; position: relative; }
清单 18-19 展示了我们如何在线的左端得到一个圆(Jay 称之为旋钮)。为了创建这个明显的空圆圈(它实际上并不空,但看起来是空的),我们对legend
元素应用了before
和after
伪选择器。before
伪选择器通过创建圆形边框,将position
值设置为absolute
,将top
和left
值设置为-7 像素,并将宽度和高度设置为 14 像素,来创建一个直径为 14 像素的圆。因为我们将背景颜色设置为白色,所以我们得到一个白色的圆圈。如果我们什么都不做,我们会有一个白色的圆圈。还记得将legend
元素上的position
属性设置为relative
吗?如果我们不将值设置为relative
,这条线看起来会以一个偏移量连接到圆,而不是在圆的中心。我们在下一个规则中展示了我们是如何得到一个看似空的圆的。
清单 18-19。在图例元素上方的直线左端添加圆
.fieldsetTreatment1 legend:before { content: '';
width: 14px; height: 14px; position: absolute; top: -7px; left: -7px; background: white; -webkit-border-radius: 100%; -moz-border-radius: 100%; border-radius: 100%; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; }
清单 18-20 展示了我们如何使legend
元素上方的行尾的圆看起来是空心的。在legend:before
伪选择器清单中,我们展示了如何在legend
元素上方的行尾画一个圆。正如清单 18-20 的描述中提到的,如果我们不做进一步的工作,我们会有一个实心的白色圆圈。我们在legend:after
伪选择器中处理这个问题。为了创建一个似乎被描边但没有被填充的圆(或者换句话说,看起来是空的),我们添加了另一个以我们的灯芯绒图像为背景的圆。这次,我们做一个 10 像素宽的圆。因为另一个圆的宽度是 14 像素,所以我们得到的是一个 2 像素描边、无填充的圆的外观。让圆排成一行的技巧是对left
和top
属性使用绝对定位和负偏移。这样,两个圆的中心都在同一位置。此外,因为圆的描边为 2,所以构成圆的线条与图例文本上方的线条宽度相同。
在一条线的末端得到一个旋钮需要很多样式。不过,我们认为值得花费时间和带宽,为我们的表单增添一点活力。
清单 18-20。做一个看起来空空如也的圆圈
.fieldsetTreatment1 legend:after { content: ''; width: 10px; height: 10px; position: absolute; top: -5px; left: -5px; background: url(/img/background_stripped.png); -webkit-border-radius: 100%; -moz-border-radius: 100%; border-radius: 100%; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; }
清单 18-21 展示了我们如何为form
元素设置填充。我们将所有form
元素的顶部和底部填充设置为 4 个像素,将左侧和右侧填充设置为 0。
清单 18-21。设置表单元素的填充
.fieldsetTreatment1 .formElement { padding: 4px 0; }
清单 18-22 展示了我们如何为form
元素设计标签样式。我们将宽度设置为 150 像素,以便为大多数标签提供足够的空间。较长的标签换行到两行,这种情况应该很少发生,发生时看起来也不差。我们还将对齐方式设置为右对齐,使标签紧挨着它所属的字段。然后,我们将右边的填充值设置为 4 个像素,因为我们不想让标签挤满它的字段。最后,我们将display
属性的值设置为inline-block
,因为否则我们无法设置width
值。
提示如果你必须支持多种语言的标签,考虑将标签放在字段上方。与大多数其他语言相比,英语相当简洁。法语或其他语言的相同标签可能更长。此外,英语比许多其他语言使用更短的单词。有时,德语中的同一个标签是一个单词,并且很长,以至于在单词分隔符之前溢出了标签空间。虽然这种情况很少见,但我们在为跨国客户工作时遇到过,因为这种问题,在最后一刻重新安排标签一点也不好玩。光处理将标签放在字段上方,因此我们可以将这种处理用于国际站点。
清单 18-22。为表单元素设计标签样式
.fieldsetTreatment1 label.structure { width: 150px; text-align: right; padding-right: 4px; display: inline-block; }
清单 18-23 展示了我们如何为暗处理设计input
元素的样式。为了与圆角主题保持一致,我们通过设置不同的border-radius
值来为input
元素创建圆角(我们需要几个值来适应不同的浏览器)。然后,我们将background-clip
的值设置为padding-box
,以使字段不出现在拐角处。接下来,我们设置一个 1 像素的中灰色边框。之后,我们将填充设置为 4 或 5 个像素,这取决于边长(一些实验使我们认为这些值看起来最好,以防您想知道为什么我们有不同的值)。最后,我们设置box-shadow
值来提供一种深度感。
清单 18-23。深色处理的造型输入元素
.fieldsetTreatment1 .inputControl input { -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; border: 1px solid #999; padding: 5px 5px 4px 4px; -webkit-box-shadow: inset 1px 1px 1px #999999; -moz-box-shadow: inset 1px 1px 1px #999999;
box-shadow: inset 1px 1px 1px #999999; }
清单 18-24 显示了我们如何设置复选框和单选按钮的边距。我们将右边距设置为 3 个像素,这样复选框或单选按钮与其标签之间就有了一点间隔。因为不同的浏览器处理复选框周围的间距不同,所以我们也将填充设置为 0(我们使用边距来获得我们想要的间距)。
清单 18-24。设置复选框和单选按钮的边距
.fieldsetTreatment1 .inputList input[type="checkbox"], .fieldsetTreatment1 .inputList input[type="radio"] { margin: 0 3px 0 0; padding: 0; }
清单 18-25 展示了我们如何将复选框或单选按钮标签的右填充设置为 10 像素,从而在标签和下一个复选框或单选按钮之间留出一些空间。
清单 18-25。设置复选框或单选按钮标签的右填充
.fieldsetTreatment1 .inputList .textLabel { padding-right: 10px; }
清单 18-26 展示了我们如何将输入列表中最后一个复选框或单选按钮的右填充设置为 0。当最后一个标签靠近列表内容区域的右边缘时,此设置可防止意外换行。虽然不是绝对必要的,但是包含它是一个很好的做法。
清单 18-26。将复选框或单选按钮的最后一个标签的右填充设置为 0
.fieldsetTreatment1 .inputList .textLabel:last-child { padding-right: 0; }
清单 18-27 显示了我们如何将text
元素插入到字段集中,使其与字段集中的字段对齐。因为标签有 150 个像素的宽度(见清单 18-22 )和 4 个像素的填充,并且因为填充与字段的边缘重叠了 1 个像素(因为我们在字段上放置了边界),我们得到字段集中text
元素的左填充值为 153 个像素。
清单 18-27。用字段排列短信
.fieldsetTreatment1 .textMessage { padding-left: 153px; }
清单 18-28 展示了我们如何让按钮与field
元素对齐。清单 18-26 中规定 153 像素的问题同样适用于此。基本上,在计算了标签的宽度和填充后,结合字段边缘的工作方式,我们最终得到 153 像素。
清单 18-28。将按钮与字段对齐
.fieldsetTreatment1 .buttonWrap { padding-left: 153px; }
清单 18-29 展示了我们如何为光处理(fieldsetTreatment2
)设置字段集的样式。首先,我们设置一个 3 像素的中灰色上边框。然后,我们在顶部设置 15 个像素的填充,以在顶部边框和字段集的内容之间提供空间。接下来,我们设置一个 20 像素的下边框,在字段集的底部和字段集后面的内容之间提供空间。最后,我们将左填充设置为 20 像素,以缩进字段集中的元素。顶部边框出现在legend
元素的后面,因为字段集的边缘穿过了legend
元素的中间(这是其设计者的意图)。我们可以通过填充值来改变它,但是我们喜欢它现在的样子。
清单 18-29。为光疗设计场景
.fieldsetTreatment2 { border-top: 3px solid #777; padding-top: 15px; margin-bottom: 20px; padding-left: 20px; }
清单 18-30 展示了我们如何在字段集控件的光照处理中设计legend
元素的样式。我们创建一个 1 点的中灰色实心边框。然后我们设置左边距为 0 像素。接下来,我们将上下填充设置为 4 像素,左右填充设置为 10 像素,以使文本远离边框。然后,我们指定圆角,并将background-clip
属性设置为padding-box
,这样可以防止背景超出边框。最后,我们将背景设置为渐变,这是用 IE9 的 SVG 图像定义的。指定渐变以使其在尽可能多的浏览器中工作需要大量的属性。
清单 18-30。用光处理为字段集设计图例
.fieldsetTreatment2 legend { border: 1px solid #777; margin-left: 0px; padding: 4px 10px; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; background: #cfeaf7; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2NmZWFmNyIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUw JSIgc3RvcC1jb2xvcj0iI2JkZGRlZCIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUxJSIgc3RvcC1j b2xvcj0iI2I3ZDFlMiIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNj YWUxZjciIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9 IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top, #cfeaf7 0%, #bddded 50%, #b7d1e2 51%, #cae1f7 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #cfeaf7), color-stop(50%, #bddded), color-stop(51%, #b7d1e2), color-stop(100%, #cae1f7)); background: -webkit-linear-gradient(top, #cfeaf7 0%, #bddded 50%, #b7d1e2 51%, #cae1f7 100%); background: -o-linear-gradient(top, #cfeaf7 0%, #bddded 50%, #b7d1e2 51%, #cae1f7 100%);
background: -ms-linear-gradient(top, #cfeaf7 0%, #bddded 50%, #b7d1e2 51%, #cae1f7 100%); background: linear-gradient(to bottom, #cfeaf7 0%, #bddded 50%, #b7d1e2 51%, #cae1f7 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#cfeaf7', endColorstr='#cae1f 7', GradientType=0); }
清单 18-31 展示了我们如何在字段集控件的光照处理中设计一个form
元素(input
元素、text
元素或按钮)。我们将上下填充设置为 5 像素,左右填充设置为 0。顶部和底部的衬垫防止form
元素相互碰撞。我们还将overflow
属性的值设置为hidden
,这样零散的文本就不会出现在form
元素之外。
清单 18-31。光处理内的造型元素
.fieldsetTreatment2 .formElement { padding: 5px 0; overflow: hidden; }
清单 18-32 展示了我们如何在字段集控件的光线处理中为form
元素设计标签样式。首先,我们确保display
的值是block
。然后,我们指定圆角,并将background-clip
属性的值设置为padding-box
,以使背景远离边界。然后我们用 SVG 指定一个背景图像,在标签后面创建一个渐变。和以往一样,创建跨浏览器渐变需要很多属性。然后,我们将文本颜色设置为深灰色。接下来,我们将float
属性的值设置为left
,以防止背景和边框跨越整个内容区域。因为我们将float
的值设置为left
,所以我们必须将clear
属性的值设置为both
,这样标签就可以保留在自己的行上。然后,我们将大多数填充值设置为 10 像素,以便在标签文本和边框之间提供足够的空间。但是,我们将底部填充设置为两个像素,以加强标签和字段之间的联系。同样为了加强这种联系,我们在标签内容区域的底部创建了一个阴影,这使得标签和字段看起来不仅仅是接触,而是彼此相连。
清单 18-32。为光处理中的form
元素设计标签
.fieldsetTreatment2 .structure { display: block; -webkit-border-top-right-radius: 5px; -webkit-border-bottom-right-radius: 0; -webkit-border-bottom-left-radius: 0; -webkit-border-top-left-radius: 5px; -moz-border-radius-topright: 5px; -moz-border-radius-bottomright: 0; -moz-border-radius-bottomleft: 0; -moz-border-radius-topleft: 5px; border-top-right-radius: 5px; border-bottom-right-radius: 0; border-bottom-left-radius: 0; border-top-left-radius: 5px; -moz-background-clip: padding; -webkit-background-clip: padding-box; background-clip: padding-box; background: #f6f8f9; background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw
Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2Y2ZjhmOSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUw JSIgc3RvcC1jb2xvcj0iI2U1ZWJlZSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjUxJSIgc3RvcC1j b2xvcj0iI2Q3ZGVlMyIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEwMCUiIHN0b3AtY29sb3I9IiNm NWY3ZjkiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3QgeD0iMCIgeT0iMCIgd2lkdGg9 IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9zdmc+); background: -moz-linear-gradient(top, #f6f8f9 0%, #e5ebee 50%, #d7dee3 51%, #f5f7f9 100%); background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #f6f8f9), color- stop(50%, #e5ebee), color-stop(51%, #d7dee3), color-stop(100%, #f5f7f9)); background: -webkit-linear-gradient(top, #f6f8f9 0%, #e5ebee 50%, #d7dee3 51%, #f5f7f9 100%); background: -o-linear-gradient(top, #f6f8f9 0%, #e5ebee 50%, #d7dee3 51%, #f5f7f9 100%); background: -ms-linear-gradient(top, #f6f8f9 0%, #e5ebee 50%, #d7dee3 51%, #f5f7f9 100%); background: linear-gradient(to bottom, #f6f8f9 0%, #e5ebee 50%, #d7dee3 51%, #f5f7f9 100%); filter: progid:dximagetransform.microsoft.gradient(startColorstr='#f6f8f9', endColorstr='#f5f7f 9', GradientType=0); color: #333; float: left; clear: both; padding: 2px 10px 2px 10px; -webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.4); -moz-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.4); box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.4); font-size: 12px; }
清单 18-33 展示了我们如何防止无关的文本被插入标签中。因为.fieldset label.structure:after
规则(见清单 18-12 )插入了一个冒号,我们需要在这里覆盖这个规则,以防止冒号被插入到光处理的标签中。
清单 18-33。在光线处理中为表单元素标签屏蔽无关文本
.fieldsetTreatment2 label.structure:after { content: ""; }
清单 18-34 展示了我们如何在光处理中设计一个input
元素。这种风格适用于由inputControl
函数创建的input
元素(参见前面的“输入控制”一节)。在许多方面,input
元素的样式与其标签的样式相同(参见清单 18-31 )。我们将 float 值设置为left
,将 clear 值设置为both
,以防止元素占用过多的空间,但仍然保持它在自己的行上。我们还设置了渐变填充。但是,填充值是不同的。我们将上下填充值设置为 4 个像素,左右填充值设置为 20 个像素,以使访问者输入的值远离边框。我们也没有在场地内设置阴影;这将是没有吸引力的,只会分散游客的注意力。
清单 18-34。光处理中的input
元素造型
`.fieldsetTreatment2 .inputControl input {
border: 1px solid #BBB;
padding: 4px 20px;
clear: both;
float: left;
-webkit-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.4);
-moz-box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.4);
box-shadow: inset 0 0 3px rgba(0, 0, 0, 0.4);
background: #EEE;
background: #e5e5e5;
background: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiA/Pgo8c3ZnIHhtbG5zPSJodHRw
Oi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgdmlld0JveD0iMCAwIDEgMSIgcHJl
c2VydmVBc3BlY3RSYXRpbz0ibm9uZSI+CiAgPGxpbmVhckdyYWRpZW50IGlkPSJncmFkLXVjZ2ctZ2VuZXJhdGVkIiBncmFk
aWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgeDE9IjAlIiB5MT0iMCUiIHgyPSIwJSIgeTI9IjEwMCUiPgogICAgPHN0b3Ag
b2Zmc2V0PSIwJSIgc3RvcC1jb2xvcj0iI2U1ZTVlNSIgc3RvcC1vcGFjaXR5PSIxIi8+CiAgICA8c3RvcCBvZmZzZXQ9IjEw
MCUiIHN0b3AtY29sb3I9IiNmZmZmZmYiIHN0b3Atb3BhY2l0eT0iMSIvPgogIDwvbGluZWFyR3JhZGllbnQ+CiAgPHJlY3Qg
eD0iMCIgeT0iMCIgd2lkdGg9IjEiIGhlaWdodD0iMSIgZmlsbD0idXJsKCNncmFkLXVjZ2ctZ2VuZXJhdGVkKSIgLz4KPC9z
dmc+);
background: -moz-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
background: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #e5e5e5),
color-stop(100%, #ffffff));
background: -webkit-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
background: -o-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
background: -ms-linear-gradient(top, #e5e5e5 0%, #ffffff 100%);
background: linear-gradient(to bottom, #e5e5e5 0%, #ffffff 100%);
filter: progid:dximagetransform.microsoft.gradient(startColorstr='#e5e5e5', endColorstr='#fffff
f', GradientType=0);
}`
清单 18-35 展示了我们如何为复选框和单选按钮(匹配inputList
类)和列表(匹配selectWrap
样式)设计标签样式。
注意这种样式适用于整个
input
元素的标签,而不是元素内单个选项(复选框或单选按钮)的标签。
因为这些标签不需要背景或阴影(也因为那些属性已经在各种祖先元素中设置好了),所以我们将那些属性设置为none
。我们还将line-height
的值设置为 14 像素,为标签的文本提供足够的空间。
清单 18-35。为列表和选择输入元素设计标签
.fieldsetTreatment2 .inputList .structure, .fieldsetTreatment2 .selectWrap .structure { background: none; -webkit-box-shadow: none; -moz-box-shadow: none; box-shadow: none; line-height: 14px; filter: none; }
清单 18-36 展示了我们如何在复选框、单选按钮(匹配inputList
类)和列表(匹配selectWrap
样式)的标签文本后插入一个冒号。
注意和
.fieldsetTreatment2 .inputControl input
样式一样(见清单 18-33 ,这种样式适用于整个input
元素的标签,而不是元素内单个选项(复选框或单选按钮)的标签。
清单 18-36。在列表和选择输入元素的标签后插入冒号
.fieldsetTreatment2 .inputList .structure:after, .fieldsetTreatment2 .selectWrap .structure:after { content: ":"; }
清单 18-37 展示了我们如何确保复选框和单选按钮之间有足够的空间(inputList
类),以及如何给列表中的选项添加左右填充(selectWrap
类)。在所有这些情况下,我们将顶部和底部填充设置为 0,将左侧和右侧填充设置为 5 个像素。
清单 18-37。对列表内容、单选按钮和复选框的标签进行样式化
.fieldsetTreatment2 .inputList .textLabel, .fieldsetTreatment2 .selectWrap .textLabel { padding: 0 5px; }
清单 18-38 展示了我们如何确保select
元素的选择箭头在 Internet Explorer 中有一个白色背景。如果没有此规则,选择箭头会出现在选择框的边框中。
清单 18-38。确保选择箭头在 ie 浏览器中有白色背景
.ie select { background: white; }
制作快捷控件
我们想用一个有用的提示来结束对控件的描述。如果您接受使用控件的想法,您可以创建一些函数来指定一些通常传递给控件的参数。这项技术让您可以制作“快捷控件”,这样您(以及使用您的控件的同事或客户)在使用控件时只需输入较少的内容。我们制作了几个应用于输入控件的例子,但是你可以为我们在本书中介绍的所有控件制作类似的快捷控件。这些快捷控件的另一个好处是,您可以确保相同的text
(和其他)元素出现在您常用的控件中,为您的站点提供更大的一致性。因此,您从您的测试团队和您的客户那里得到的错误报告会更少。这总是一件好事。
让我们从制作电子邮件input
元素的控件开始。因为我们可以提供一些值,所以我们得到了一个接受五个参数而不是十个参数的函数。清单 18-38 显示了一个调用inputControl
函数来创建电子邮件输入框的函数。
清单 18-39。一个电子邮件输入控件
function emailInput($id, $name, $value, $class, $echo) { $output = inputControl($id, "Email", $name, $value, "email", "name@domain.com", $class, TRUE, null, "return"); if ($echo != "return") { echo $output; } else { return $output; } }
清单 18-40 显示了一个创建密码input
元素的快捷控件。在这种情况下,我们可以将参数减少到四个。
清单 18-40。一个密码输入控件
function passwordInput($id, $name, $class, $echo) { $output = inputControl($id, "Password", $name, null, "password", "Type a Unique Password", $class, TRUE, "return"); if ($echo != "return") { echo $output; } else { return $output; } }
清单 18-41 显示了一个快捷控件,用于创建一个让访问者重新输入密码的input
元素。
清单 18-41。重新输入密码的控件
function reenterPasswordInput($id, $name, $class, $echo) { $output = inputControl($id, "Reenter Password", $name, null, "password", "Retype Password Exactly", $class, TRUE, "return"); if ($echo != "return") { echo $output; } else { return $output; } }
我们希望你能从例子中看到如何轻松地创建自己的快捷控件库。
总结
这一大章讲述了如何制作控件来制作表单中的元素。我们提供了两种治疗方法,光和暗。虽然我们根据它们最显著的视觉特征称它们为浅色和深色,但它们还有其他重要的区别。从可用性的角度来看,最重要的是深色处理在与input
元素相同的行上有标签,而浅色处理在input
元素上方有标签。正如我们在本章正文中提到的,这种区别可能会使轻度处理更适合国际访问者经常访问的网站。
我们还展示了如何使用before
和after
伪选择器来创建两种效果:一个围绕元素的包装器(我们在form
元素的黑色处理周围添加了一个)和一个在线条左侧的空圆(实际上是圆中的圆)。我们发现before
和after
伪选择器用得不多,所以我们想用它们展示一些技巧。
在这个过程中,我们展示了一项技术,您可以将它用于我们所有的控件(以及您自己的控件,如果您开始编写控件的话):制作快捷控件。快捷控件实际上只是用指定的一些参数调用其他控件的控件。通过仔细规划,您可以从一个更小的更通用的控件集合中构建一个大型的快捷控件库。让这种范式发挥作用的诀窍是让你的基本控件尽可能具有可扩展性。如果这听起来像面向对象编程,那么你是对的。在面向对象编程中,从一小组基本对象中创建一大组对象是一项常见的任务。
最后,我们想再次强调一个关键点:结构合理的 HTML 和 CSS 提供了巨大的重用潜力和设计灵活性。我们可以通过创建另一个处理(它实际上只是一组 CSS 类和控件中的处理的名称)来快速地将我们的任何控件重新用于我们没有预料到的用途。即使您不接受控件的概念,也要记得编写 HTML,这样就可以用不同的样式表来设计不同的样式。这样,你就可以尽可能地让你的工作“经得起未来的考验”。
第一部分:简介
这本书是关于创建高性能网站的。它的重点是大型和高容量的网站。我们是在为一家公司工作时认识的,这家公司的网站有超过 50,000 页,每月有超过 80,000,000 的访问者(在假日购物季会有更多)。然而,这本书给出的建议同样适用于较小的网站,这些网站的访问量几乎没有这么大。不管网站的复杂性或流量负载如何,毕竟每个人都想要好的性能。
我们在书中讨论三种表现:
- 客户端(即浏览器)性能
- 服务器端和网络性能
- 开发者表现
正如这个列表所暗示的,我们涵盖了如何获得尽可能好的页面加载时间,如何尽可能地限制 HTTP 请求和带宽使用,以及开发人员如何重用内容。在本书的最后三分之二,我们详细介绍了一个系统,开发人员可以通过该系统创建可重用的组件,然后使用它们来构建页面。这项技术是我们如何提高开发人员绩效的终极课程。虽然我们致力于制作可重用的组件并利用它们构建页面,但我们仍然专注于提供建议和技术,并辅以代码示例,以最大限度地提高客户端和服务器端的性能。
在这一过程中,我们提供了解决一些更棘手的 web 开发问题的技术,比如设计可以单独处理的标签和总是正确呈现的引导符。我们还展示了如何通过使用一些鲜为人知的 CSS 选择器,包括:before
和:after
伪选择器,用 CSS 创造视觉趣味。
换句话说,我们已经写了一本关于性能的书,然后加入了其他的技巧和诀窍。我们希望您喜欢它,并发现其中介绍的一些技术很有用。
第二部分:性能基础
本书的这一部分讲述了我们的开发方法,如何提高页面加载时间(客户端性能),我们对响应式 web 设计的使用,以及 Web 重用模式。基本上,我们是在设置背景,这样本书的其余部分才有意义。
我们涵盖了关注点分离对于前端开发人员是如何有用的,为什么我们接受渐进式增强,以及为什么我们认为您也应该接受它。我们还将介绍浏览器如何加载网页,因为如果不了解浏览器如何呈现页面,就很难谈论客户端性能。
然后,我们将介绍适用于所有页面的基本性能准则。如前所述,我们讨论了如何改善页面加载时间,以及如何通过使用尽可能少的带宽来最小化网络压力。我们也解释了为什么页面加载时间很重要。然后我们讨论每个单独的指导方针,包括帐篷中的长杆:减少 HTTP 请求。如果我们没有成功教会你什么,我们希望至少向你展示如何限制从服务器获取东西。
接下来,我们讨论伊森·默科特的优秀技术:响应式网页设计。我们讨论如何使用媒体查询、灵活的图像和灵活的网格来创建在几乎任何设备上都很好看的页面。除了简单地调整元素和图像的大小,我们还会随着显示的变大而添加内容。
请注意,我们不提倡在显示器变小时去掉内容;相反,我们采用移动优先模式,从我们打算支持的最小设备开始。采用渐进式改进包括为拥有更好设备的访问者提供更好的演示。我们希望你从增加一个最小的(但仍然是好的)演示文稿的角度来思考,而不是从一个完整的演示文稿中减去。
这一部分探讨了我们的核心概念之一:在多个演示中重用内容。我们把同一内容的每次交替呈现称为一种处理。使用相同的 HTML 结构,我们应用不同的样式集。最后,我们展示了我们如何依赖 CSS 嵌套,以及 CSS 无声地失败(也就是说,没有给访问者错误消息)来获得我们想要的表示。
第三部分:建立一个网站
在本节中,我们将在第二章到第五章中给出的所有性能建议应用到一个实际的网站([
clikz.us](http://clikz.us)
)。因为电子商务网站通常比博客或新闻列表需要更复杂的布局,所以我们以电子商务网站为例。此外,当我们构思这本书时,我们正在为一家非常大的电子商务公司工作,这就是我们心中的问题。
本节由两个小节组成。第一个是第六章到第九章,处理出现在每个页面上的元素:HTML 样板文件、导航元素、标题和页脚。
首先,我们展示如何创建页面模板。我们使用[
www.html5boilerplate.com](http://www.html5boilerplate.com)
来获得一个包含许多有用选项的基础页面。然后,我们通过条件语句让事情在各种版本的 Internet Explorer 上正常工作。我们还处理其他细节,比如处理兼容模式、加载 jQuery 和添加 Google Analytics。最后,我们设置了站点的网格,用来控制页面各个部分的位置。
然后我们讨论为什么一个网站应该为不同议程的访问者提供不同种类的导航。然后我们展示如何创建两种不同的导航。我们从菜单开始,为那些喜欢浏览他们想要的东西的访问者。然后,我们在菜单上添加一个搜索框,供那些喜欢搜索而不是浏览的人使用。我们还讨论了如何让我们的导航在不支持 CSS3 甚至不支持 JavaScript 的浏览器上工作。
接下来是为每一页的顶部创建一个报头。在冒险之前,我们先处理一个常见的问题:国家选择器。虽然我们自己的站点没有使用国家选择器,但是我们展示了如何让国家选择器尽可能好地运行。我们使用一个绝对定位元素的列表来创建我们的报头,并讨论如何使用 CSS 剪辑来处理部分图像。最后,我们给出关于报头的基本建议:保持简洁。
然后我们处理如何为我们的网站制作页脚。重用我们菜单中的内容(第七章),我们把它变成一个位于页面底部的站点地图。然后我们添加法律块。在这个过程中,我们讨论了为什么 SVG 是其他图像格式的一个很好的替代品。然后,我们演示如何用 SVG 创建一个图像(我们将在本书的剩余部分使用它),以及如何通过 JavaScript 与 SVG 图像进行交互。
第二小节处理构建我们在整个示例站点中重用的控件。我们引入了“分形”设计模式的概念——在这种模式中,复杂的控件由简单的控件组成。每个控件实际上都是一个带有样式表的 PHP 函数,每个控件都需要特定的 HTML 结构。通过研究每个控件,我们添加了一些您可能会发现在使用控件和进行其他 web 开发时有用的细节。
这本书的一个关键概念是分形(或嵌套)控件。我们首先讨论模式的一般特征。在讨论该模式时,我们展示了 label 控件——我们的第一个也是最简单的控件。然后是一个基于我们之前工作的案例研究(我们是在做那份工作时认识的,那份工作让我们有了写一本书的想法)。我们还将讨论何时将 CSS 与 JavaScript 分离,何时将它们结合起来(以及如何结合)。
我们接下来讨论为什么我们开发链接控件,然后展示我们是如何做的。我们详细介绍了构成控件的功能和样式。(我们添加了一个 JavaScript 组件,为不能通过 CSS 显示工具提示的浏览器提供故障转移能力。)
接下来,我们讨论为什么以及如何创建一个包含链接的盒子。我们认为边栏对于提供关于产品的补充信息是有用的,这些信息在页面的主要部分是不合适的。组成 sidebox 控件内容的链接实际上是链接控件。还记得嵌套控件的分形概念吗?sidebox 控件的链接提供了一个例子。
接下来,我们将详细解释我们的按钮控件,并包括七种不同的处理方法。正如我们在详述我们的 web 重用模式时所讨论的,我们通过从一个处理到下一个处理重用 HTML 来实践我们所宣扬的。通过改变风格,我们得到了七种不同的治疗方法。
然后我们详细介绍价格控制,这是我们最复杂的控制之一。首先,它实际上是两个控制:价格控制和运输控制。另一方面,价格控制意味着重复,以创造我们所说的“价格栈”。在描述价格和运输控制时,我们解决了一个经常被忽视的问题:如何让一行点——一个点前导——从商品的末尾到价格的开头。
然后,我们展示如何创建产品列表。同样,这个想法是通过调用一个相当简单的函数来创建一个复杂的页面组件。与任何其他控件相比,产品控件更多地使用控件中控件的范例。产品控件包含价格、运输、链接和按钮控件。此外,产品控件是第一个使用数据对象的控件,我们将其构造为 JSON 对象。
接下来,我们将介绍创建表格的控件。我们提供了两种处理方式:一种是带有标题行的表格,另一种是左侧带有标题内容的表格(可以说是标题列)。我们还创建了不使用 table、tr 和 td 元素以及使用这些元素的表。大多数浏览器可以正确显示不使用传统表格元素的表格。对于那些不能处理现代表格呈现的浏览器,我们提供了传统元素。
然后,我们继续讨论如何创建一组选项卡。我们再次提供两种处理方式,水平(浅色)和垂直(深色)。为了克服我们在许多网站上发现的选项卡的一些恼人之处,我们制作了选项卡,以便每个选项卡都可以是一个唯一的地址。这样,用户可以链接到一个单独的选项卡,并通过浏览器历史记录返回。我们还确保我们的选项卡内容正确居中,即使一个选项卡有单行标签,另一个有多行标签。最后,我们通过动画显示从一个选项卡到另一个选项卡的转换,增加了一点视觉趣味。
我们通过描述两个让我们更容易制作表单的控件来结束本书。我们定义了一个字段集控件和一个输入控件。顾名思义,fieldset 控件创建一个 fieldset 元素及其子元素(输入元素和其他内容)。输入控件创建一个支持所有可能类型(复选框、单选按钮等)的单一输入元素。).作为我们分形范例的一个例子,字段集控件使用输入控件。在这个过程中,我们展示了如何用:before
和:after
伪选择器创建有趣的视觉效果。最后,我们展示了一种适用于所有控件的技术:制作快捷控件。