原文:Pro HTML5 Performance
协议:CC BY-NC-SA 4.0
八、报头
在最后一章的马拉松之后,我们认为你可能想要休息一下。因为我们没有太多关于报头的内容要说,所以我们决定用这个简短的章节来平衡这个长章节。我们对报头的基本建议是保持简洁。不要试图把所有的东西(或者任何超出基本的东西)都塞进报头。
“报头”一词来源于帆船时代。想象一下,一根桅杆上挂着一堆巨大的方形帆,顶部有一面旗帜(字面意思是在桅杆的顶端)。帆完成了所有的工作。这面旗帜赢得了所有的荣誉,因为它承载着对那艘船上的人和其他船上的人都非常重要的信息。通过显示国籍,它是船上的人的骄傲,也是其他人的朋友或敌人的指示器。
当报纸变得司空见惯时,他们采用了刊头的概念作为部分版面的隐喻。一份报纸的大部分版面都被自己的“帆”占据了:它的版面——传递信息的大块区域,包括新闻报道和广告。刊头标识纸张,就像旗帜标识船只一样。这个比喻是一个方便的提醒,即刊头的工作是识别或标记报纸。换句话说,在报纸和网站上,刊头是识别品牌的地方,也是展示其他方便信息的地方。
在网页设计中,标题是品牌标志的位置,是设置网站外观和感觉的起点。此外,报头通常包含重要的导航项目:“关于我们”、“联系我们”、“购物车”等。在下面的例子中,我们使用电子商务网站上常见的内容:徽标、帐户、购物车和电话。国际网站上常见的一个项目是国家/语言选择器。虽然我们没有在示例站点中包括它,但是我们提到了一种加速这个特性的性能技术。
构建更好的国家选择器
在许多国家销售的大型网站通常会提供一个下拉菜单,包含他们提供的每个国家/语言选项。图 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
`
报头 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(../https://blog.csdn.net/wizardforcel/article/details/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(../https://blog.csdn.net/wizardforcel/article/details/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。*为菜单设置变量值
`
<?php $navContext = "nmDropDown"; $assistiveNav = "Main Navigation"; include ("includes/siteNavigation.php"); ?> `
要为菜单设置正确的值,将清单 9-3 中的代码放在页脚的顶部。
***清单 9-3。*为页脚设置变量值
`
<?php $navContext = "footerNav"; $assistiveNav = "Footer Navigation"; include ("includes/siteNavigation.php"); ?> `
因此,在每个实例中,我们都定义了一段由服务器处理的代码。这就是<?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 ||
t
e
x
t
=
=
"
"
)
∣
∣
(
text =="") || (
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 ‘’;
// In that case, you’d probably want to comment out our eye-watering
// error message:
echo ‘’ .
$e->getMessage() . ‘’;
}
// Let’s start building our string, starting with the first bit of the
// opening tag for our link and an href attribute:
$output = “<a href='” . $href . “'”;
// The following ternary operator checks to see that our $type argument
// has a value, and if it does we’ll add the “data-link-type” attribute
// along with the value of $type (we’ll see later what the
t
y
p
e
d
o
e
s
)
:
type does):
typedoes): output .= ( ($type == NULL || $type == “”) ? “” : " data-link-type=‘" .
t
y
p
e
.
"
′
"
)
;
/
/
A
d
d
a
n
I
D
i
f
t
h
e
r
e
′
s
o
n
e
p
r
e
s
e
n
t
,
w
i
t
h
y
e
t
a
n
o
t
h
e
r
t
e
r
n
a
r
y
o
p
e
r
a
t
o
r
.
type . "'"); // Add an ID if there's one present, with yet another ternary operator.
type."′"); //AddanIDifthere′sonepresent,withyetanotherternaryoperator. output .= ( ($id == NULL || $id == “”) ? “” : " id=’" .
i
d
.
"
′
"
)
;
/
/
A
d
d
o
u
r
g
e
n
e
r
i
c
l
i
n
k
c
l
a
s
s
:
id . "'"); // Add our generic link class:
id."′"); //Addourgenericlinkclass: output .= " class=‘link";
// Another ternary to pass in a class if there’s one set:
o
u
t
p
u
t
.
=
(
(
output .= ( (
output.=((class == NULL || $class==“”) ? “” : " " .
c
l
a
s
s
)
;
/
/
D
e
s
c
r
i
p
t
i
v
e
t
e
x
t
f
o
r
t
h
e
l
i
n
k
:
class); // Descriptive text for the link:
class); //Descriptivetextforthelink: output .= "’>" . $text;
// Add the link icon and, if
t
o
o
l
t
i
p
M
e
s
s
a
g
e
h
a
s
a
v
a
l
u
e
,
/
/
w
e
′
l
l
a
d
d
a
n
a
t
t
r
i
b
u
t
e
t
o
d
r
i
v
e
t
h
e
C
S
S
:
/
∗
∗
F
o
r
f
o
l
k
s
w
h
o
l
i
k
e
t
e
r
n
a
r
y
o
p
e
r
a
t
o
r
s
,
h
e
r
e
′
s
a
s
i
n
g
l
e
−
l
i
n
e
w
a
y
t
o
a
d
d
t
h
e
i
c
o
n
a
n
d
m
e
s
s
a
g
e
:
tooltipMessage has a value, // we'll add an attribute to drive the CSS: /* * For folks who like ternary operators, here's a single-line way to add the icon and message:
tooltipMessagehasavalue, //we′lladdanattributetodrivetheCSS:/∗ ∗Forfolkswholiketernaryoperators,here′sasingle−linewaytoaddtheiconandmessage: output .= ( ($type == NULL || $type == “”) ? “” : “<span class=‘linkIcon’” . ( $type == “help” ? " data-tooltip-message=‘" .
t
o
o
l
t
i
p
M
e
s
s
a
g
e
.
"
′
"
:
"
"
)
.
"
>
<
/
s
p
a
n
>
"
)
;
∗
/
‘
‘
/
/
W
e
i
n
c
l
u
d
e
t
h
e
o
t
h
e
r
w
a
y
f
o
r
g
r
e
a
t
e
r
r
e
a
d
a
b
i
l
i
t
y
(
i
f
(
tooltipMessage . "'" : "" ) . "></span>"); */` `// We include the other way for greater readability ( if (
tooltipMessage."′":"")."></span>");∗/‘‘//Weincludetheotherwayforgreaterreadability( if(type != NULL &&
t
y
p
e
!
=
"
"
)
type != "")
type!="") output .= <span class=‘linkIcon’"
if (type == “help”)
$output .= " data-tooltip-message=’" .
t
o
o
l
t
i
p
M
e
s
s
a
g
e
.
"
′
"
;
tooltipMessage . "'";
tooltipMessage."′"; output .= “>”
$output .= “”;
// (If the ternary operators are too hard to follow, try breaking them up
// into if-else blocks.) Sometimes, we need to return the link string to
// other functions for further processing. Other times (most of the time,
// really), we want to output the link where it’s being called in the HTML.
// So we pass a string in for the
e
c
h
o
a
r
g
u
m
e
n
t
t
h
a
t
d
e
f
a
u
l
t
s
t
o
e
c
h
o
i
n
g
/
/
t
h
e
o
u
t
p
u
t
i
f
a
n
y
t
h
i
n
g
b
e
s
i
d
e
s
"
r
e
t
u
r
n
"
i
s
p
a
s
s
e
d
.
I
n
t
h
i
s
w
a
y
w
e
c
a
n
/
/
n
o
t
s
e
t
i
t
f
o
r
m
o
s
t
o
f
o
u
r
u
s
e
s
.
H
e
r
e
′
s
a
(
c
o
m
m
e
n
t
e
d
)
t
e
r
n
a
r
y
o
p
e
r
a
t
o
r
/
/
t
h
a
t
w
o
u
l
d
d
o
t
h
e
j
o
b
:
/
/
(
echo argument that defaults to echoing // the output if anything besides "return" is passed. In this way we can // not set it for most of our uses. Here's a (commented) ternary operator // that would do the job: // (
echoargumentthatdefaultstoechoing //theoutputifanythingbesides"return"ispassed.Inthiswaywecan //notsetitformostofouruses.Here′sa(commented)ternaryoperator //thatwoulddothejob: //(echo != “return”) ? echo $output : return
o
u
t
p
u
t
;
/
/
H
e
r
e
′
s
t
h
e
r
e
p
l
a
c
e
m
e
n
t
f
o
r
t
h
a
t
t
e
r
n
a
r
y
o
p
e
r
a
t
o
r
,
i
n
c
a
s
e
y
o
u
′
r
e
t
i
r
e
d
/
/
o
f
t
e
r
n
a
r
y
o
p
e
r
a
t
o
r
s
:
i
f
(
output; // Here's the replacement for that ternary operator, in case you're tired // of ternary operators: if (
output; //Here′sthereplacementforthatternaryoperator,incaseyou′retired //ofternaryoperators: if(echo != “return”) {
echo $output;
} else {
return $output;
}
}
?>`
如您所见,我们添加了许多注释来描述基本功能。为了在我们的 HTML 中实现这一点,我们将在需要插入基本链接的地方编写以下代码:
<?php mLink("Regular Link", "page.html"); ?>
您将得到一个类似于以下元素的 link 元素:
<a href="page.html" class="link">Regular Link</a>
但是那很无聊,那么我们还能用我们的函数做什么呢?正如清单 11-2 所示,很多。
***清单 11-2。*mLink 函数的各种调用
`<?php mLink("Regular Link", "page.html"); ?>
<?php mLink("External Link", "http://somesite.com", "external"); ?>
<?php mLink("Email Link", "mailto:me123@thefakeemail.com", "email"); ?>
<?php mLink("Help Link", "javascript:;", "help", NULL, NULL, NULL, "Can it really be this easy?"); ?>`
清单 11-3 显示了 HTML 结果中的元素。
***清单 11-3。*清单 11-2 的 HTML 结果元素:
<a href='page.html' class='link'>Regular Link</a><br/><br/> <a href='page.html' data-link-type='internal' class='link'>Internal with Icon<span class='linkIcon'></span></a><br/><br/>
<a href='http://somesite.com' data-link-type='external' class='link'>External Link<span class='linkIcon'></span></a><br/><br/> <a href='mailto:me123@thefakeemail.com' data-link-type='email' class='link'>Email Link<span class='linkIcon'></span></a><br/><br/> <a href='javascript:;' data-link-type='help' class='link'>Help Link<span class='linkIcon' data- tooltip-message='Can it really be this easy?'></span></a>
注意,我们没有设置id
属性,没有添加额外的类,也没有指定返回另一个函数而不是回显 HTML 文档。在每种情况下,指定指定的参数都会生成相应的属性,这似乎很容易。
图 11-1 显示了这些元素在浏览器中的样子。
***图 11-1。*链接看起来怎么样
注意你可以在clikz.us/BookExamples/linkControl.php
看到图 11-1 中的链接在浏览器中的样子
设计链接
我们使用 CSS 来生成链接的图标,并控制链接控件的各种表现形式的外观。内部链接的图标可能没什么用处。在许多网站中,大多数链接都是内部的,这样显示会增加太多的混乱。相反,你可能会发现外部链接对你的用户来说信息量更大。这是一个很好的可用性标准,让访问者知道他们什么时候离开你的网站,他们可能会信任你的网站,去另一个他们可能不信任的网站。电子邮件链接也表示点击该链接的结果。最后,我们将使用帮助图标(“?”);添加关于特定链接或信息片段的附加信息似乎已经成为事实上的惯例。
因为我们已经标准化了这些类型的链接,所以我们可以使用非常特定的 CSS 和 JavaScript 来给我们提供额外的功能。例如,如果 CSS 动画可用,访问者会看到链接以跳动的方式从当前颜色变为绿色的漂亮动画。这创造了一个非常像应用的感觉。我们将在本章稍后向您展示该动画。
如果访问者的鼠标悬停在链接旁边的图标上,我们可以让工具提示出现。对于内部、外部和电子邮件链接,工具提示消息是静态的。但是,对于帮助链接,我们可以创建一个自定义消息。自定义消息是函数的最后一个参数$tooltipMessage
。图 11-2 显示了带有工具提示的链接。
***图 11-2。*链接图标及其含义
我们还构建了能够忽略不必要参数的控件(除了我们认为必要的href
和descriptive text
)。出于性能原因,我们不想生成任何不明确需要的 HTML。
我们没有列出一个庞大的清单,而是把它分成许多较小的清单,每个清单都有一个介绍。我们还为更复杂的规则添加了注释(比如控制动画的规则)。
清单 11-4 显示了基本的链接规则。
***清单 11-4。*基本链接规则
a.link { display: inline-block; color: #14699F; padding: 0; }
清单 11-5 显示了如何获得“悸动”动画。因为我们希望它能在所有可能支持它的浏览器上运行,这是一个很长的列表。
***清单 11-5。*造型雷动悬停效果
`/*
* This hover state calls a keyframe, called “throb”, declared below
* this ruleset with duration, iteration count, easing (timing
* function), and fill-mode, which tells the animation to end on the
* 100% properties if the hover state is left.
*/
a.link:hover {
text-decoration: none;
-webkit-animation-name: throb;
-webkit-animation-duration: 1s;
-webkit-animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
-webkit-animation-fill-mode: forwards;
-moz-animation-name: throb;
-moz-animation-duration: 1s;
-moz-animation-iteration-count: infinite;
-moz-animation-timing-function: linear;
-moz-animation-fill-mode: forwards;
animation-name: throb;
animation-duration: 1s;
animation-iteration-count: infinite;
animation-timing-function: linear;
animation-fill-mode: forwards;
}
/*
* Our keyframe “throb” makes the link above start with its original
* blue color and transition to green with a smooth animation. Then
* back to blue. It repeats infinitely so long as the link is in the
* hover state. The percentages are based on the animation-duration
* defined in the ruleset above it. So in this case 100% will be one
* second long.
*/
@-webkit-keyframes throb {
0% {
color: #14699F;
}
50% {
color: #169f1d;
}
100% {
color: #14699F;
}
}
@-moz-keyframes throb {
0% {
color: #14699F;
}
50% {
color: #169f1d;
}
100% {
color: #14699F;
}
}
@keyframes throb {
0% {
color: #14699F;
}
50% {
color: #169f1d;
}
100% {
color: #14699F;
}
}`
在这一点上,提醒一下我们试图实现的视觉效果和功能可能会有所帮助。图 11-3 显示了一个包含各种链接的无意义内容的表格。标题行有帮助链接,第二列有外部链接,最后一列有电子邮件链接。
***图 11-3。*显示不同种类链接的表格
清单 11-6 定义了一个悬停效果,显示给浏览器不支持悸动效果的访问者。
***清单 11-6。*当我们不能让它跳动的时候创造一个悬停效果
/* * If a browser doesn't support CSS3 animations we still want to show a hover state. */ .no-cssanimations a.link:hover { color: #169f1d; }
清单 11-7 为所有类型的链接(除了简单链接)设置了一些公共属性(position
和padding-right
)。
***清单 11-7。*设置链接的公共属性(简单链接除外)
/* * This looks for all anchors with an attribute of "data-link-type" * and a class of "link". We'll use this to set common CSS * properties for link controls that have a "data-link-type" * attribute set. This saves repeating the properties in each type. */ a[data-link-type].link { position: relative; padding-right: 23px; }
清单 11-8 显示了我们如何为保存链接图标的区域设置属性。同样,这些属性不适用于简单链接,因为它没有图标。
***清单 11-8。*设置链接图标属性
/* * Same as 11-6 but for the "linkIcon" span we inserted in the link * control for our link $types */
a[data-link-type].link .linkIcon { background-image: url(/https://blog.csdn.net/wizardforcel/article/details/img/clikz-sprite.png); background-repeat: no-repeat; width: 16px; height: 16px; position: absolute; right: 3px; top: 0; }
清单 11-9 展示了一个巧妙的技巧(嗯,我们认为它很巧妙)。这个想法是当访问者的鼠标悬停在链接图标上时插入一个元素。这有点棘手,因为它依赖于在元素的三面设置边框。人们自然希望在四面都设置边界,但这对我们的目的来说是错误的。最终结果是一个突出显示链接图标的三角形——一种直观连接工具提示消息和它所描述的图标的便捷方式。
***清单 11-9。*用三角形高亮显示当前链接图标
/* * This is a little trickier. We're creating a nested element inside * the "linkIcon" span by adding the pseudo :before class only when * you hover on the "linkIcon" span. This :before is used to make * the triangle that appears to the right of our tooltip message. * The effect is accomplished by setting only three sides of the * border, with the top and bottom being transparent. The background * of the triangle is set by defining the right border color. */ a[data-link-type].link .linkIcon:hover:before { content: ""; position: absolute; right: -6px; top: 3px; width: 0; height: 0; z-index: 99; border-top: 5px solid transparent; border-right: 6px solid #14699F; border-bottom: 5px solid transparent; }
清单 11-10 定义了工具提示消息的容器。我们使用伪:after
类来为消息创建空间和样式。
***清单 11-10。*为工具提示消息创建容器和样式
/* * This is the container for our tooltip message. As above, we use a * pseudo class but this time it's the :after class. We also add a * ".tooltipMessage" selector as a fallback if the :after pseudo * class isn't available (as in older browsers). */
`a[data-link-type].link .linkIcon:hover:after, .tooltipMessage {
content: ‘’;
background: #14699F;
width: 200px;
position: absolute;
z-index: 99;
right: -206px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
-moz-background-clip: padding;
-webkit-background-clip: padding-box;
background-clip: padding-box;
color: white;
padding: 2px 5px 3px 10px;
-moz-box-sizing: border-box;
box-sizing: border-box;
min-height: 16px;
}`
清单 11-11 定义了内部链接的位置和工具提示内容。
***清单 11-11。*定义内部链接的位置和工具提示内容
/* * INTERNAL LINK */ /* * This positions the background image we declared in the ruleset * above, "a[data-link-type].link .linkIcon", so we can see the * proper icon. */ a[data-link-type="internal"].link .linkIcon { background-position: -137px -54px; } /* * Here we set the text of our tooltip. */ a[data-link-type="internal"].link .linkIcon:hover:after { content: "This link stays within this site."; }
清单 11-12 定义了外部链接的位置和工具提示内容。
***清单 11-12。*定义外部链接的位置和工具提示内容
/* * EXTERNAL LINK */ a[data-link-type="external"].link .linkIcon { background-position: -159px -53px;
} a[data-link-type="external"].link .linkIcon:hover:after { content: "This link will take you to another site."; }
清单 11-13 定义了电子邮件链接的位置和工具提示内容。
***清单 11-13。*定义电子邮件链接的位置和工具提示内容
/* * EMAIL LINK */ a[data-link-type="email"].link .linkIcon { background-position: -116px -54px; } a[data-link-type="email"].link .linkIcon:hover:after { content: "This link will send an email."; }
清单 11-14 定义了帮助链接的位置和工具提示内容。因为我们希望帮助链接的工具提示内容是开发人员传递给函数的内容,所以我们使用 CSS 很少使用的content
函数将内容放入工具提示中——这是另一个方便的技巧。
***清单 11-14。*定义帮助链接的位置和工具提示内容
/* * HELP LINK */ a[data-link-type="help"].link .linkIcon { background-position: -92px -54px; } /* * We're using a rarely seen function of CSS that really works only for * the content property: attr (name of an attribute for which we want * the value). This attribute has to be on the selector for our ruleset. */ a[data-link-type="help"].link .linkIcon:hover:after { content: attr(data-tooltip-message); }
清单 11-15 展示了我们的“如果其他都失败了”规则。如果我们不能做任何其他事情,我们将隐藏 JavaScript 在:after
和:before
功能不可用时创建的工具提示消息,您将在下一节看到。
***清单 11-15。*设定回退条件
/* * This is also for our fallback, so we start with a hidden state * that we'll show with some JavaScript. */ .tooltipMessage { display: none; }
当 CSS 失败时使用 JavaScript
有了 CSS,我们可以做很多事情来使我们的代码非常高效,这是令人惊讶的。这里使用的 CSS 也执行得很好,因为浏览器在本地处理它,而不是作为脚本定义的额外功能。然而,如果这个更现代的 CSS 不被支持,我们仍然希望保留我们的工具提示功能,至少对于帮助链接。当然,通过使用下面的方法,你也可以保留静态链接上的描述。或者您可以默认使用自己喜欢的方法来显示工具提示。无论如何添加故障转移功能,都要确保在函数调用周围添加 Modernizr ( if(!Modernizr.generatedcontent){}
)检查。
为了补充我们的 HTML,我们在功能上严重依赖 CSS。然而,当 CSS 不工作时(因为有人有一个旧的或者功能不太好的浏览器),我们使用 Modernizr 来触发一个 JavaScript 解决方案。(如果那个访问者也不使用 JavaScript,我们就被困住了,但是因为那些访问者可能习惯于非常简单的网站,相比之下,我们的网站看起来并不差。)
为了在没有 CSS 帮助的情况下添加工具提示功能,我们创建了一个 jQuery 插件。您当然可以使用其他方式,但是我们喜欢 jQuery。清单 11-16 显示了包含 jQuery 插件的脚本以及如何用 Modernizr 调用它。
***清单 11-16。*一个使用 Modernizr 来调用它的 jQuery 插件
`
//Attach this method to jQuery
$.fn.extend({
//Your plugin’s name goes here
tooltip : function() {
//Iterate over the matched elements
return this.each(function() {
//plug-in code goes here
var t = $(this),
message = t.attr(“data-tooltip-message”);
t.find(“.linkIcon”).append(“
”+ message + “ ”);
var tooltipMessage = t.find(“.tooltipMessage”);
t.hover(function(){
tooltipMessage.show();
},function(){
tooltipMessage.hide();
})
});
}
});
})(jQuery);
if(!Modernizr.generatedcontent){
$(“a[data-tooltip-message]”).tooltip();
}
`
我们已经编写了一个 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(KaTeX parse error: Expected '}', got 'EOF' at end of input: linkArray){ StringBuilder .= ‘
- ’;
for ($i=0, s i z e = s i z e o f ( size = sizeof( size=sizeof(linkArray); $i < $size; KaTeX parse error: Expected '}', got 'EOF' at end of input: i++) { StringBuilder .= ‘
- ’;
S t r i n g B u i l d e r . = m L i n k ( StringBuilder .= mLink( StringBuilder.=mLink(linkArray[$i][0], l i n k A r r a y [ linkArray[ linkArray[i][1], l i n k A r r a y [ linkArray[ linkArray[i][2],
l i n k A r r a y [ linkArray[ linkArray[i][3], “return”);
KaTeX parse error: Expected 'EOF', got '}' at position 29: … .= '</li>'; }̲ 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 = “<” . h t m l E l ; / / P u t i n o u r h r e f a t t r i b u t e . htmlEl; // Put in our href attribute. htmlEl; //Putinourhrefattribute. output .= " href=‘" . h r e f . " ′ " ; / / G e t a b a s e c l a s s o f b u t t o n . href . "'"; // Get a base class of button. href."′"; //Getabaseclassofbutton. output .= " class=‘button";
// If there’s a type declared, get the proper classes that give our
// different treatments.
o u t p u t . = ( ( output .= (( output.=((type != NULL || t y p e ! = " " ) ? " " . g e t T y p e C l a s s e s ( type != "") ? " " . getTypeClasses( type!="")?"".getTypeClasses(type) : “”);
// If addition classes are present pass them in. This ability allows
// us greater flexibility.
o u t p u t . = ( ( output .= (( output.=((class != NULL || $class != ‘’) ? " " . c l a s s . " ′ " : " ′ " ) ; / / I f a t i t l e i s d e c l a r e d , a d d i t . I f ( class . "'" : "'"); // If a title is declared, add it. If ( class."′":"′"); //Ifatitleisdeclared,addit. If(title != NULL && KaTeX parse error: Expected '}', got 'EOF' at end of input: …e != "") { output .= " title=’" . KaTeX parse error: Expected 'EOF', got '}' at position 16: title . "'"; }̲ output .= “>”;
$output .= t e x t ; i f ( text; if ( text; if(icon == TRUE) {
o u t p u t . = " < s p a n c l a s s = ′ b u t t o n I c o n " ; i f ( output .= "<span class='buttonIcon"; if ( output.="<spanclass=′buttonIcon"; if(iconClass != NULL && KaTeX parse error: Expected '}', got 'EOF' at end of input: …!= "") { 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 t y p e . f u n c t i o n g e t T y p e C l a s s e s ( type. function getTypeClasses ( type.functiongetTypeClasses(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”:
break;
default:
return “”;
}
}
?>`正如我们之前提到的,相同的目的可以在许多不同的语言中实现。我们只是碰巧在使用 PHP(我们不得不使用一些东西,我们发现 PHP 很容易阅读)。
我们在这里没有任何幻想。唯一看起来奇怪的部分是包含了按钮作为锚元素或按钮元素的能力。如果您需要在表单中放置一个按钮,您可能希望使用一个按钮元素(这样更容易附加
submit
行为等等)。在表单之外,您可能希望使用锚定元素,因为这样的按钮与锚定元素具有相同的效果。同样,您现在可能已经认识到了这种模式。为了实例化我们的按钮控件,我们在希望按钮出现在 HTML 中的位置调用带有所需参数的函数。清单 13-2 显示了创建按钮的代码。
***清单 13-2。*创建按钮
`<?php button("Primary", "javascript:;", NULL, NULL, "primary") ?>
<?php button("Primary Button with Icon", "#", NULL, NULL, "primary", "btn_checkmark", TRUE, "icon_checkmark"); ?>
<?php button("Primary Glass Button", "javascript:;", NULL, NULL, "primaryGlass") ?>
<?php button("Primary Shadow Button", "javascript:;", NULL, NULL, "primaryShadow") ?>
<?php button("Primary Outset Button", "javascript:;", NULL, NULL, "primaryOutset") ?>
Primary Go Button
<?php button("GO", "javascript:;", NULL, NULL, "primaryGo") ?>
Primary Go Outset Button
<?php button("GO", "javascript:;", NULL, NULL, "primaryGoOutset") ?>
`清单 13-3 显示了插入到结果文档中的 HTML 标记。
***清单 13-3。*函数调用产生的 HTML】
`Primary
Primary Button with Icon
Primary Glass Button
Primary Shadow Button
Primary Outset ButtonPrimary Go Button
GO
Primary 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)
总结
在这一章中,我们为一个元素创建了另一个控件,这个控件似乎不值得我们去做。虽然我们理解第一眼的评价,但我们认为稍加思考就会明白为什么为按钮创建一个控件是有意义的。最大的原因是按钮到处都是。大多数网站,尤其是电子商务网站,几乎每个页面上都有多个按钮。几乎总有一种方法可以购买某些东西,定制某些东西,与支持人员或销售代理进行聊天,等等。考虑到按钮的数量,我们肯定希望它们的外观和行为都一致。按钮控件是让他们合作的好方法。
第二(但仍然是一个很大的好处),我们创造了一些东西,让我们的队友不必考虑如何制作按钮。如果他们使用我们的控制,他们会得到一个按钮,保证符合公司的外观和行为标准。因此,他们的发展速度增加;简而言之,他们用更少的时间完成更多的工作。我们怎么能不喜欢呢?
第三(但仍然很重要),维护更容易。假设出于商业考虑(比如公司范围内的品牌重塑工作),必须改变所有的按钮。我们改变一个代码块,控件,网站上所有的按钮都会改变。
此外,我们不得不提到封装的概念。按钮有问题吗?只需看看按钮控件。当您的问题被隔离在一个控件中时,故障诊断会快很多。
综上所述,这些优势以很低的成本提供了强大的功能和灵活性。这就是为什么我们喜欢我们的按钮控件,尽管乍一看它似乎有点过头了。