CSS 高流量网站高级教程(三)

原文:Pro CSS for High Traffic Websites

协议:CC BY-NC-SA 4.0

七、设备

在高流量的网站上,不寻常的设备比其他设备构成了更多的访问者。根据你网站的目标人群和你的报告工具的结果,你可能需要认真考虑这些设备。当我们从 CSS 的角度讨论设备时,实际上我们只是在谈论具有不同功能的浏览器。无论我们的 CSS 是提供给手机,笔记本电脑,还是搜索引擎蜘蛛,本质上它只是另一个网络浏览器,具有不同的分辨率和不同的能力来处理我们的 CSS。

每种设备都有不同的考虑因素;使用打印机时,我们需要注意分页符和灰度的清晰度,以及避免打印不必要的内容。对于移动设备,我们需要关注不寻常的屏幕尺寸和分辨率,以及缓慢的处理器或较差的 CSS 支持。屏幕阅读器也可以被认为是设备,但是我们在前一章已经详细讨论过了。

在本章中,您将了解

  • 媒体类型
  • 媒体查询
  • 媒体特征
  • 详细打印
  • 具体的 SEO 注意事项
  • 移动设备
  • 其他设备

媒体类型

您的网站收到的流量越多,被不太可能或不寻常的设备访问的可能性就越大。虽然较小的网站可以忽略或忽视特定的设备和/或网络浏览器,但对于较大的网站来说,它们可能构成足够大的数量,需要作为重要的用户群来考虑。大公司非常重视他们的品牌和客户体验,至少你应该向任何有兴趣访问你网站的人展示合理的体验,不管他们用什么来浏览。为此,您需要实现各种方法来检测它们的平台并提供最合适的内容。

我们用来引用外部样式表的 link 标签中的 media 属性旨在标识样式表所针对的设备类型。如果设备/浏览器支持,它们将忽略不相关或未知的样式表媒体类型,并且不包括文件的内容。值得注意的是,大多数现代浏览器仍然会下载文件,即使它们不打算解析内容。 1 这意味着从性能角度来看这种方法没有用——它实际上比不使用它会招致更多的 HTTP 请求——而只对特定的浏览器或设备有用。

你实际上可以通过三种方法来定位媒体。第一个是在链接标签内,就像这样:

<link rel="stylesheet" href="style.css" media="**screen**" />

第二种是通过@import 指令:

@import url(style.css) **screen**;

@import 命令必须始终位于样式表中任何其他规则之前(其他@import 命令除外)。同样,大多数现代浏览器下载文件,即使它们不会解析文件的内容。无论如何,我们不建议您使用@import 规则,因为它们会阻止对文件其余部分的解析。

最后一种方法是@media 指令,它允许我们只使用特定媒体类型的文件的特定部分。这里有一个例子:

@media **screen** {    body {       font-family: arial;    } }

这只针对报告支持屏幕媒体类型的浏览器和设备。

对于所有这三种方法,您可以用逗号分隔媒体值,以针对多种类型,如下所示:

`

@import url(style.css) screen, print;

@media screen, print {
   body {
      font-family: arial;
   }
}`

在这些情况下,如果浏览器或设备仅支持其中一种媒体类型,仍将解析文件/规则。

对于每种媒体类型,都有一些重要的因素需要考虑。这些被称为媒体组

分页媒体(由页面组成的媒体,如打印的文档,而不是像屏幕上显示的网页一样的长文档)支持一些连续媒体没有的额外属性 2 网格媒体(与位图媒体相反)认为固定宽度字符很重要。例如,盲文媒体需要字符具有可预测且一致的宽度,以便用户能够理解。我们可以按静态、交互或两者来分组,表明目标是只读的还是可以交互的。媒体还可以分为视觉、听觉、语音(对于屏幕阅读器)或触觉(对于通过触摸进行交流的设备;例如盲文)。


这是为了使它们的内容仍然可以通过 CSS 对象模型(CSSOM)来访问,并被列在 document.styleSheets 中,可以通过 JavaScript 来访问 document . style sheets,以给出附加到文档的所有样式表的详细信息。

我们将在本章后面提到它们。

让我们来看看 CSS2 规范中定义的媒体类型。

全部

虽然不是真正的媒体类型,但 all 适用于以下所有媒体类型,因此适用于所有媒体组。如果不指定媒体类型,这是默认行为。

盲文

盲文是一种传达字符的触觉方法,主要由盲人使用。盲文触觉反馈设备的功能各不相同。该规范将这种媒体类型描述为连续的而不是分页的(可以输出分页媒体的盲文打印机被认为是在浮雕媒体类型中)、触觉的、网格的以及交互和静态的。

浮雕

浮雕介质类型类似于盲文类型,但旨在通过盲文打印机输出。因此,它被认为是分页的(而不是连续的)和静态的。除此之外,它与盲文类型完全相同。

手持

手持型旨在瞄准手机等手持设备。这些设备通常连接速度较慢,屏幕较小,因此需要区别对待。由于手持设备的功能差异很大,它们被认为是除了触觉以外的所有媒体的一部分。??

手持设备的性能从差到强,以及介于两者之间,并提供许多不同的浏览器。由于它们变化如此之快,我们建议使用媒体查询(将在本章后面介绍)来定位它们,而不是仅仅依赖媒体类型。但是请记住,您不能依靠媒体类型或媒体查询来控制下载的内容;浏览器或设备可能会选择下载所有内容,因此没有性能优势。

由于目前的手持设备,如智能手机和平板电脑,尽最大努力呈现网络,而不是网络的精简版本,因此大多数设备实际上都是“屏幕”媒体类型的目标。WebKit (Mobile Safari 和 Android)和 Firefox Mobile 完全忽略这种媒体类型。如果用户在设置中切换到“手持模式”,或者文档具有已知的移动 doctype,Opera Mini 和 Opera Mobile 将尊重它。

打印

打印介质类型专门适用于打印介质输出,因此被视为分页、可视、网格和静态。我们将在本章后面详细讨论印刷媒体和分页媒体。


事实上,自 2009 年 9 月以来,诺基亚已经开始在手机上试验盲文阅读器。

o 投影

投影媒体类型用于投影输出,通常是投影仪。因为这种输出通常来自无人终端,所以被认为是分页的、可视的和位图的。规范将它列为仅交互,我们认为这是一个错误。Opera 在全屏呈现模式(Opera Show)下尊重这种媒体类型。我们不知道任何其他浏览器或设备实现了这一点。

屏幕

画面媒体类型无疑是最常见的类型。主要用于电脑屏幕(台式机和笔记本电脑),这是你最有可能使用的媒体类型,与一起使用。几乎所有的设备都使用这种媒体类型,不管它是否是为他们设计的,因为这些设备中的大多数都打算在 web 浏览器中呈现互联网,而不是一个简化的版本。屏幕被认为是连续的、位图的,既有交互的也有静态的,既有视觉的也有听觉的。

演讲

语音媒体类型适用于任何能够以声音方式阅读页面内容的设备或应用程序,例如屏幕阅读器或其他辅助技术。在旧的规范中,它被称为听觉(现在已被否决),所以最安全的做法是同时针对两种媒体类型,就像这样:

<link rel="stylesheet" href="style.css" media="**speech, aural**" />

这种媒体类型被认为是连续的、语音的、交互的和静态的。网格或位图媒体组在这种情况下不适用。你可以在[www.w3.org/TR/CSS2/aural.html](http://www.w3.org/TR/CSS2/aural.html)阅读更多关于这种特定类型的样式表。

tty

这种媒体类型(是“电传打字机”的缩写)适用于使用“固定间距字符网格”(即宽度完全相同的字符网格)的媒体。由于这种媒体类型没有字符像素大小的概念,作者不应该提供它们。目前这种类型的设备非常少,支持互联网的就更少了,所以对这种媒体类型的支持和使用非常少。这种媒体类型被认为是连续的、可视的、网格的、互动的和静态的。

电视

这种媒体类型是为电视机设计的。尽管规范将这些设备列为“低分辨率、彩色、有限滚动屏幕、可用声音”,但电视机的性能差异很大,这种描述通常是不正确的。当前的大型电视通常比它们的监视器提供更高分辨率的屏幕,有时甚至包括网络浏览软件和滚动能力。由于这些功能上的不一致,电视媒体类型被认为是连续的和分页的、可视的、音频的、位图的、交互的和静态的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注:**虽然屏幕和印刷品是目前最常用的媒体类型,但我们知道除了tty之外,还有使用所有媒体类型的设备。媒体类型被添加到规范中,但不一定由设备制造商实现,这通常是因为开发人员很少实现。

其他设备可能支持此列表中未定义的媒体类型,但我们建议不要使用它们。您可以在[www.w3.org/TR/CSS2/media.html](http://www.w3.org/TR/CSS2/media.html)了解更多关于媒体类型的信息。由于媒体类型现在很少足以描述设备或浏览器的功能,CSS3 引入了媒体查询,它为我们提供了必要的工具来根据设备的特性和功能而不是(或与)其媒体类型来定位设备。大多数当前的移动设备(包括平板电脑)都支持媒体查询,如果您打算使用这些设备,我们建议您使用它们。

媒体查询

媒体类型可以让我们有能力针对我们可能期望浏览我们网站的一些设备,但它们在设备中的实现是不完整的。新设备的支持更加可预测,尤其是那些高流量网站可能会有大量访客的设备。同样,这些设备可能只占你总流量的一小部分,但仍然是很大一部分人。媒体查询让我们能够更好地控制目标设备和浏览器。

媒体查询由两部分组成:一个媒体类型和零个或多个将根据媒体特征进行检查的查询。当媒体类型与设备类型匹配并且所有查询都为真时,应用媒体查询。如果列出了多个媒体查询(使用逗号分隔的列表),则只有一个媒体查询需要为真。

这里有一个简单的例子:

<link rel="stylesheet" href="style.css" media="screen and (min-width:800px) and (max-width:1500px)" />

除了针对支持“屏幕”媒体类型的浏览器和设备,我们还声明设备的视窗宽度必须至少为 800 像素,最多为 1,500 像素。我们在上面括号中命名的功能称为“媒体功能”。您可以将逗号分隔的媒体查询(与媒体类型完全一样)作为速记,如下所示:

<link rel="stylesheet" href="style.css" media="tv, projection, screen and (min-width:800px) and (max-width:1500px)" />

在这种情况下,每个逗号分隔的值都是作为一个整体进行计算的,(跟在“和”后面的部分仅适用于紧接在它们前面的媒体类型)。前面的媒体查询与下面的相同:

`

`

每个列表中可以使用多个“and”子句。您还可以使用“not”来否定整个媒体查询。但是,您不能否定查询的个别部分。以下将起作用:

<link rel="stylesheet" href="style.css" media="not screen and (min-width:800px) and (max-width:1500px)" />

这将只解析任何浏览器或设备的 style.css,如果不应用“not”关键字,查询将失败(不支持媒体类型screen,或者视口宽度小于 800 像素,或者视口宽度大于 1500 像素)。以下(以及尝试相同操作的其他变体)不起作用:

<link rel="stylesheet" href="style.css" media="screen and (min-width:800px) and not (max-width:1500px)" />

对于任何习惯于基本条件逻辑的开发人员来说,这种粒度的缺乏都是令人沮丧的。要以这种方式定位多个功能集,必须首先将默认行为应用于所有设备,然后为每个设备覆盖此行为,如下所示:

`/* Applied to every device and browser */
body {
   color: black;
}

@media screen and (min-width:800px) {
   /* Only applied to screen devices with a viewport width of at least 800 pixels */
body {
   color: red;
   }
}`

每当用户代理(浏览器或设备)不理解媒体查询时,它就被认为是“不是全部”——即,它将不应用链接的样式表或查询的内容。

“only”关键字可用于确保只有符合 CSS3 的浏览器才会应用该样式表。

<link rel="stylesheet" href="style.css" media="only screen" />

在媒体查询的开头使用“only”关键字将对不支持它们的旧浏览器隐藏媒体查询(因为它们会认为“only”是媒体类型的名称),而兼容的浏览器将忽略“only”关键字并检查其后的媒体查询。

和在link标签中一样,媒体查询可以在 CSS 文件中以另外两种方式使用,就像媒体类型可以单独使用一样:

`@import url(style.css) screen and (min-width:800px) and (max-width:1500px);

@media screen and (min-width:800px) and (max-width:1500px) {
   body {
      font-family: arial;
   }
}`

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**浏览器会下载链接的文件,即使这些文件不会应用于相关文档。

当媒体特性将评估为非零值(不考虑单位)时,您不需要提供值。例如:

<link rel="stylesheet" href="style.css" media="all and (width)" />

该样式表将总是被应用,因为视口的宽度将总是大于零(可能主要是听觉设备除外)。你不会使用这种方法;我们只是把它作为一个例子。

在我们深入了解可用的媒体特性之前,有必要花点时间讨论一下视口本身。视区是设备上可用的呈现大小,不包括任何浏览器镶边。

随着 Mobile Safari 的推出,苹果还推出了viewport meta tag4

viewport meta标签给你一些对视窗大小和行为的控制。虽然这是 HTML,但理解我们将在本章中涉及的一些概念是很重要的。这里有一个简单的例子:

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

名称“viewport”标识了这个meta标签。内容属性包含我们想要传递的值。属性是用等号分开的键/值对;您可以用逗号分隔它们,想要多少就有多少。我们可以使用两个重要的常数:

  • 设备宽度:设备的实际像素宽度(横向)
  • 设备高度:设备的实际像素高度(横向)

还有六个我们可以使用的重要属性:

  • width :视口的初始宽度(横向),以像素为单位。该值也可以是这两个常数中的任意一个。默认值(在 Mobile Safari 上)为 980 像素。
  • height :视口的初始高度(横向),以像素为单位。该值也可以是这两个常数中的任意一个。默认值是根据设备的宽度和纵横比自动设置的。
  • 初始比例:比例是一个乘数,用于计算视口的大小。例如,如果我们将视口宽度设置为 400 像素,比例设置为 1.5,则视口实际上将为 600 像素,并在可视区域之外水平延伸 200 像素。此属性设置初始比例值。默认情况下,它通常会被自动设置为适合可见区域中整个网页的值。用户可以缩放设备来调整该值。
  • 最小比例:设置用户可以缩放到的最小比例。
  • 最大比例:设置用户可以缩放到的最大比例。
  • 用户可缩放的(user-scalable):设置用户是否可以调整视窗的大小——是否可以缩放。潜在值为。默认值为是。

4 你可以在developer . apple . com/library/safari/# documentation/apple applications/Reference/safari html ref/Articles/meta tags . html阅读关于 viewport 元标签的内容,以及在developer . apple . com/library/safari/# documentation/apple applications/Reference/safari web content/usingthe view port/usingthe view port . html阅读关于 viewport 的具体内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示: Opera 提出了用 CSS 实现同样事情的方法,而不是用 HTML,这样更合适。遗憾的是,目前还没有真正的支持,但是你可以在[dev.w3.org/csswg/css-device-adapt/](http://dev.w3.org/csswg/css-device-adapt/)阅读规范。

重要的是,视窗可能会超出可视区域,但绝不会小于可视区域。这很重要,因为我们在媒体特性中使用了viewport。CSS3 规范中定义了许多媒体功能。我们将在下面的章节中列出这些。

宽度

宽度媒体功能适用于视窗(连续媒体)或“页面框”(分页媒体)的宽度。 5 有虚拟视口的地方(即比例大于 1)这适用于整个视口的宽度,而不仅仅是可渲染的屏幕空间。

它接受大于零的数字(任何小于零的数字都会使查询无效)。您可以(像许多媒体功能一样)在 width 前面加上min-max-,就像上面的例子一样。当分别大于或等于和小于或等于时,它们实际上起相同的作用。 6

可以使用任何单位,但是在使用相对大小的单位时,这些单位将相对于文档的根元素(即html标签)。

身高

高度媒体功能的行为与其“宽度”功能完全相同,但应用于视口的高度。

设备宽度

设备宽度媒体特性描述了设备的整个呈现表面(即可显示区域)的宽度。对于屏幕,这是屏幕的宽度。对于分页媒体,这是页面的宽度。同样,您可以使用max-min-作为前缀,任何小于零的值都将导致无效的媒体查询。这使用实际的屏幕宽度,而不是您可能已经用 viewport meta标签设置的虚拟视窗,或者用户可能已经通过在他的设备上缩放页面而修改的虚拟视窗。

设备高度

除了高度之外,设备高度媒体功能与device-width完全相同。


在 www.w3.org/TR/2009/CR-CSS2-20090908/page.html#page-box的 CSS 2.1 规范中定义的 5

我们决定避免使用<和>字符,因为它们可能会与 HTML 中的字符冲突。

方位

方向媒体特征有两个可能的值:landscapeportrait。如果宽度视口大于高度,则方向为横向。否则就是人像。如果在查询中使用此媒体功能,您应该始终向其传递一个值。使用不带值的方向会导致浏览器或设备不应用 CSS。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**没有方向的“平方”值。如果宽度和高度值完全相等,则方向被视为纵向。

长宽比

纵横比媒体特征是视口的宽度与高度之比,用正斜杠分隔。这里有一个简单的例子:

@media screen and (aspect-ratio: 4/3) {    … }

设备纵横比

device-aspect-ratiomedia 特性描述了设备的整个可呈现区域的宽度与高度之比,用正斜杠分隔。

颜色

颜色媒体特性描述了设备每个颜色组件的位数。例如,如果器件的红、绿、蓝三色各使用四位,则该值为四。如果由于某种原因,每个组件使用了不同的位数,则最小的位数就是值。如果设备不是彩色的,或者不是可视的,这个数字将为零。您可以将min-max-用于彩色媒体功能。

颜色指数

颜色索引功能用于查询设备一次可以显示的颜色数量。这通常比彩色媒体功能更直观。设备通常支持 16 个;256;65,536;或者 16,777,215 种颜色。您可以将min-max-color-index一起使用,非彩色或不可视的设备会将该值显示为零。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**在媒体查询中,您可以在数值中使用逗号,这对易读性有很大帮助。下面的例子就可以了:

    <link rel="stylesheet" href="style.css" media="screen and (min-color-index:1,001)" />

单色

单色媒体特性描述了设备支持单色显示的位数(实际上,数字越大,可以显示的黑白灰度就越多)。您可以将min-max-用于此媒体功能,如果设备是彩色的或没有显示,该值将为零。

分辨率

分辨率媒体特性以每英寸点数(DPI)或每厘米点数(DPCM)来描述设备上的像素密度;您应该在媒体查询中使用这些单位(分别为 DPI 和 DPCM)。此媒体功能支持min-max-前缀。如果使用带有非方形像素和min-resolution的设备,将使用密度最小的尺寸(DPI 较小的尺寸)。使用max-resolution时,将比较最密集的尺寸。如果对非方形像素使用“分辨率”(用于精确匹配),则样式表永远不会被应用。此介质功能仅适用于基于位图的介质,将其应用于网格介质会导致整个介质查询被视为“非全部”

扫描

扫描媒体功能专门适用于电视(以及tv媒体类型)。适用值为“渐进”和“扫描”。如果设备不是电视机,则该值将评估为零。

网格

网格媒体功能描述设备是基于网格还是基于位图。对于网格输出设备,该值为 1;否则为 0。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **提示:**媒体查询将被即时评估。也就是说,如果您已将媒体查询设置为应用于视窗小于 900 像素的设备,并且您正在使用具有可调整视窗大小的设备,则调整窗口大小将导致查询立即被应用或取消应用。

您可以在[www.w3.org/TR/css3-mediaqueries/](http://www.w3.org/TR/css3-mediaqueries/)了解更多关于媒体查询的信息。

自从在 WebKit 中引入 CSS 转换、过渡和动画以来,规范中定义的基本媒体特性显然不足以只针对支持它们的浏览器。为此,WebKit 定义了四个专有媒体功能,将在以下几节中讨论:

二维变换

transform-2d media 特性描述了 CSS 变换在二维空间中的可用性。如果可用,该值将计算为 1,如果不可用,将计算为 0。

变换-3d

这个媒体特性描述了 CSS 转换在三个维度上的可用性。如果可用,该值将计算为 1,如果不可用,将计算为 0。由于支持跨二维的 CSS 转换是该特性可用的必要条件,因此您可以根据该特性的值 1 安全地推断出transform-2d的值也为 1。

过渡

过渡媒体特征描述 CSS 过渡的可用性。如果可用,该值将计算为 1,如果不可用,将计算为 0。

动画

动画媒体特征描述了 CSS 过渡的可用性。如果可用,该值将计算为 1,如果不可用,将计算为 0。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**遗憾的是,你不能用“not (-webkit-animation)”这样的话来定位不支持这些媒体功能的设备媒体查询中的任何未知特征将立即使媒体查询等同于“不是全部”。对于这些设备,您需要设置默认行为,然后覆盖它,就像我们之前演示的那样。

虽然在[webkit.org/specs/MediaQueriesExtensions.html](http://webkit.org/specs/MediaQueriesExtensions.html)[developer.apple.com/library/safari/#documentation/appleapplications/reference/SafariCSSRef/Articles/OtherStandardCSS3Features.html](http://developer.apple.com/library/safari/#documentation/appleapplications/reference/SafariCSSRef/Articles/OtherStandardCSS3Features.html)描述并记录了所有这四种媒体特征,但事实上它们(在撰写本文时)无法在实践中使用。它们都需要特定于供应商的 WebKit 前缀才能正常工作。以下是工作介质功能的完整列表:

  • -webkit-transform-2d
  • -webkit-transform-3d
  • -webkit-transition
  • -webkit-animation

使用媒体查询可能会令人沮丧,但重要的是要记住规范一直在发展。现在,这里有四个要点可以帮助您在开发和使用媒体查询时避免困惑:

  • “not”关键字否定整个查询。您不能否定媒体查询的单个元素。
  • 在介质查询中没有“或”运算符(尽管您可以用逗号分隔整个介质查询)。
  • 无论媒体查询如何,都将下载外部引用的文件。不要假设将媒体类型应用于单独的文件会给你带来任何 HTTP 性能上的好处。相反,这实际上会导致更多的文件请求。
  • 特定于 WebKit 的媒体功能需要供应商前缀。

现代化

由开发人员 Faruk Ateş、Paul Irish 和 Alex Sexton 创建的 Modernizr ( [www.modernizr.com/](http://www.modernizr.com/))是一个 JavaScript 库,用于检测和公开浏览器功能。它的工作原理是向页面的html元素添加类;这些类指示浏览器中是否存在特定的特性(准确地说是 HTML5 和 CSS3 特性)。Modernizr 不支持浏览器中的功能;它只是说明它是否可用。它还支持在 Internet Explorer 等功能较弱的浏览器中设计新的 HTML5 元素。

要使用 Modernizr,您只需要在 HTML 文档的头部链接到它,就像这样:

<script src="js/modernizr-1.6.min.js"></script>

其次,您需要在您的html标签中添加一个“no-js”类。这将是页面的默认状态。如果 JavaScript 关闭,Modernizr 将无法工作,您将能够检测到这一点,并向您的用户提供页面的非 JavaScript 版本:

<html class="no-js">

如果启用了 JavaScript,类似于下面的各种类将替换该类:

<html class="js canvas canvastext geolocation rgba hsla multiplebgs borderimage borderradius boxshadow opacity no-cssanimations csscolumns no-cssgradients no-cssreflections csstransforms no-csstransforms3d no-csstransitions video audio cufon-active fontface cufon-ready">

在前面的示例中,我们可以看到有问题的浏览器(在本例中为 Firefox 3.6)不支持 CSS 动画、CSS 渐变、CSS 反射、3D 中的 CSS 变换或 CSS 过渡(分别来自类“no-cssanimations”、“no-cssgradients”、“no-cssreflections”、“no-csstransform3d”和“no-csstransitions”)。它支持其他功能,如画布和地理定位。

有了这些知识,我们现在可以相应地编写 CSS。假设我们想给页面的body元素添加多个背景。我们可以首先为所有浏览器指定默认样式,然后用一个更具体的样式覆盖该样式,该样式只针对支持该功能的浏览器,如下所示:

body {         background: url(simple.png) top left repeat-x;
} .multiplebgs body {         background: url(multiple-top.png) top left repeat-x,         url(multiple-bottom.png) bottom left repeat-x; }

在前面显示的html元素的例子中,Firefox 3.6 可以理解并显示多种背景。例如,Internet Explorer 8 将只显示“simple.png”背景图像,因为它使用的类将是“no-multiplebgs”。

同样重要的是要注意,在前面的例子中,Firefox 3.6 实际上会下载这两个图像,因为这两个规则都可以应用(即使不太具体的规则被覆盖),所以即使我们确保满足用户可以和不可以看到这些功能,我们也通过让他们下载完全不必要的文件来惩罚那些可以看到这些功能的用户。我们可以通过使用"来避免这种情况。在我们的第一条规则中,不要只说“body”

尽管这是一个简单而有用的解决方案,但在您考虑使用它之前,请记住,Modernizr 会产生非常冗长的 CSS,所有用户都可以下载它,但不一定会应用它,而且它会在您的 CSS 中添加一个 JavaScript 依赖项,这远非理想。

打印样式表

打印样式表需要被认为是一个特例。用户经常想要打印你网站上的页面,你可以用不同于一般用户的方式来满足他们的需求。打印你的内容的用户是有兴趣的用户,用不必要的内容或不适合他们媒体的页面来打击他们会降低他们对你的网站和你的公司的好感。另一方面,以清晰友好的形式向他们提供他们需要的信息表明了对细节的关注和对他们需求的考虑。制作一个基本的打印样式表是非常容易的,如果你的标记和 CSS 是经过深思熟虑的,它通常会非常简洁,并且在你的整个网站上运行良好。

包含打印样式表很容易。我们将在此重申如何最好地实现这一目标:

<link rel="stylesheet" href="print.css" media="print" />

您可以使用@import 命令来实现同样的事情,但是这对于非常旧的浏览器来说是行不通的。您还可以使用@media 命令专门针对打印设备,但这同样会排除较旧的浏览器。对于打印您的内容的用户,您需要考虑在这个样式表中包含几件事情。

  • 用户想要的是页面中的内容,而不是导航或其他多余的元素(如表单和其他在静态媒体中无用的交互元素,以及广告,除非你特别约定在打印时显示它们,这是不太可能的)。在这些元素上考虑一个类似“noprint”的类,这样就很容易用display:none;隐藏它们。或者,您可以使用一个类,比如“print ”,只用于那些您希望输出到打印机的元素。
  • 考虑一下您是想要扩展您的屏幕样式表(通过使用媒体类型all作为该样式表)来维护现有品牌,还是完全替换它(通过使用媒体类型screen作为该样式表)。完全替换它通常会产生良好的结果,并避免不可预测的陷阱,这在您有大量页面需要考虑时是很常见的。
  • 衬线字体在印刷媒体上更容易阅读(屏幕媒体上呈现的信息则相反)。考虑另一种衬线字体堆栈。
  • 避免在打印样式表中更改字体大小。浏览器默认值通常是合适的。
  • 如果你改变字体大小,以磅为单位(pt)。12 分一般没问题。
  • 将任何容器调整到页面的宽度(100%)并移除它们的边距。这将确保它们填充被移除元素的空间以及可打印区域。这也有助于确保它们不会溢出到第二个水平页面上,从而导致糟糕的打印体验。您还应该使用@page 规则在打印的页面上添加一个小的页边距,这一点我们将在本节稍后讨论。这将有助于舒适的阅读,尤其是在能够进行无边框打印的打印机上。
  • 不要让你的容器漂浮。这可能会产生不可预知的结果。
  • 默认情况下,打印时通常禁用背景图像和颜色,因此请确保您指定的深色(或黑色)字体颜色在白色背景下清晰可见。
  • 如果打印背景图像或颜色,请将背景设置为白色。这可以避免模糊或不清楚的内容,可以节省墨水,并使页面打印速度更快。
  • 出于与前面相同的原因,移除不需要背景的元素上的背景(将其设置为透明)。
  • 链接在印刷媒体上不明显,也没有悬停状态。给你的链接加下划线,这样可以清楚地表明它们是链接。我们将在这一节的后面讨论处理链接的其他巧妙方法。
  • 使用适合介质的单位,如磅(pt)、英寸(in)和厘米(cm)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**打印样式表会阻止 Internet Explorer 中的渲染。因此,您可能会考虑推迟用 JavaScript 加载这些内容。

Eric Meyer 在他的优秀文章 List Apart ( [www.alistapart.com/articles/goingtoprint/](http://www.alistapart.com/articles/goingtoprint/))中建议使用:after伪元素将链接到的 URL 附加到它们的链接文本之后。这非常有意义;如果没有 URL,链接文本在打印时实际上是无用的。下面是他实现这一目标的简化代码:

a:after {         content: " (" attr(href) ") "; }

他接着说,这种方法不能很好地处理相对于网站根的链接,并为此提供了一个解决方案:

a[href^="/"]:after {         content: " (http://www.alistapart.com" attr(href) ") "; }

这些解决方案并不完美。相对于当前文档的 URL 仍然不会显示它们的完整路径,并且句末的链接会在右括号后和句号前显示一个空格,但这是对另一种选择的巨大改进。

这是一个打印的列表页示例:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 7-1。*列表打印输出示例

印刷和其他分页媒体支持连续媒体的一些额外规则和属性。页面本身可以使用@page在全局级别应用边距,如下所示:

@page {    margin: 2cm 3cm; }

您可以改为将边距应用于body元素,但是这样您就不会在每页的底部获得边距。

左右页也有伪类。打印双面文档时,第二页是“左”页,第三页是“右”页,第四页是“左”,依此类推。在用户将这些页面装订在一起的情况下,通常考虑页面之间更大的装订线区域。这很容易实现:

`@page:left {
   margin-right: 4cm;
}

@page:right {
   margin-left: 4cm;
}`

您还可以使用:first伪类专门针对第一页。第一页既不被视为左侧也不被视为右侧:

@page:first{    margin: 10cm; }

控制分页符

有几个属性的存在是为了让我们更好地控制页面的破损位置。前三个是特定于分页符的:

  • page-break-before
  • page-break-after
  • page-break-inside

page-break-beforepage-break-after接受五个可能的值(如果包括“inherit”,则为六个,这是它们的默认值):

  • auto
    • 这对打印输出没有影响。每当打印机(或分页设备)空间不足时,就会拆分页面。
  • always
    • 在元素之前(或之后,视情况而定)总是强制分页。
  • avoid
    • 设备将尝试避免在元素之前(或之后,视情况而定)分页。
  • left
    • 设备将在前面(或后面,视情况而定)插入分页符,这样下一页将是“左”页(因此可以用:left伪类作为目标)。
  • right
    • 设备将在前面(或后面,视情况而定)插入分页符,这样下一页将是一个“右”页(因此可以用:right伪类作为目标)。

page-break-inside仅支持该列表中的“自动”和“避免”。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **提示:**如果你在表格中使用了 thead、tbody 和 tfoot 标签,页眉和页脚会在每一页上重复,使表格更容易阅读。表格行仍可能被拆分,这可以通过在打印样式表中使用以下 CSS 来避免:

tr {   page-break-inside: avoid; }

您可以在[www.w3.org/TR/CSS2/tables.html](http://www.w3.org/TR/CSS2/tables.html)阅读更多关于表格打印行为的信息。

从可访问性和可用性的角度来看,构建打印样式表是一件容易且值得做的事情。大型网站并不总是迎合打印机,因此给用户的体验很差;采取这一步骤有助于将你的网站与其他网站区分开来。同样,对于一个高流量的网站,越来越多的用户会想要打印你的页面,可能是因为他们没有支持移动互联网的设备。在图 7-2 、 7-3 和 7-4 中有现实生活中的打印样式表的例子。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-2。 Gap 不会隐藏不必要的元素或将其文本设置为深色,因此,大部分文本是不可见的,并且第一个打印页面没有有用的内容。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 7-3。*维珍的网站在进入任何内容之前就打印出一大堆乱七八糟的东西。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 7-4。《洋葱》让广告妨碍了它的内容。

一如既往的浏览器实现不一致,你还是需要测试你的代码。您可以在打印预览中快速检查您的工作结果,而不是杀死树木。您还可以快速地将您的link标签的媒体类型(或者@import 或@media 规则)切换到“all”,但是请注意,默认情况下,浏览器可能会将不同的样式应用于打印样式表,而不是屏幕样式表。你可以在[www.w3.org/TR/2009/CR-CSS2-20090908/page.html#page-box](http://www.w3.org/TR/2009/CR-CSS2-20090908/page.html#page-box)阅读针对分页媒体的 CSS 2.1 规范。CSS3 增加了更多的功能,允许您控制页码计数器、寡妇行(页面顶部左侧的行)和孤儿行(页面底部左侧的行)。虽然现在的支持是不完整的,但实现这些也无妨。在[dev.w3.org/csswg/css3-page/](http://dev.w3.org/csswg/css3-page/)阅读更多关于他们的信息。

移动设备

你的用户访问你的网站的方法一直在急剧增长。你的许多典型用户现在可能有几种手段来访问互联网:家用电脑,工作电脑,也许是某种机顶盒,和移动电话。手机已经从 20 世纪 80 年代的砖块形状走了很长一段路,现在许多手机本身就是有能力的电脑。他们可以通过许多不同的技术访问您的数据,包括 WiFi、GPRS、2G 和 3G,并且更快的标准一直在出现。对于高流量的网站,将你的数据提供给移动设备的可能性非常高,你的用户会对你有很高的期望。事实上,发展中国家的用户可能会完全绕过电脑,只拥有一台移动设备。平板电脑也在迅速普及,可以被视为移动设备。

向移动设备提供网站服务时,您需要特别考虑一些事情:

  • 延迟:移动 web 开发人员的主要眼中钉,延迟是一个不可避免的问题。无论移动互联网连接速度有多快,延迟很可能永远是个问题。这意味着减少 HTTP 请求比以往任何时候都更重要。
  • 小屏幕尺寸:移动设备的屏幕可能比你习惯使用的小得多。
  • 像素密度:移动设备上的一些屏幕现在有足够高的像素密度(或者对于平板电脑来说足够大),以至于你的媒体查询很容易将它们误认为桌面屏幕。
  • 方向:现在很多移动设备都支持从横向切换到纵向,反之亦然。你的媒体查询应该利用这一点。 7
  • 较低的处理能力:你将渲染卸载到 GPU 的聪明方法,以及你实现的所有过渡和变换不会以每秒三帧的速度给任何人留下深刻印象。
  • 电池寿命:访问网络和做任何 CPU 密集型的事情都会以高于正常的速度消耗用户的电池。
  • 成本:一些用户可能会为他们的带宽付费。他们不会欣赏不必要的网络访问或未经优化的图像。
  • 功能:某些功能(特别是:focus:hoverposition:fixed)可能在这些设备上不可用。

地理定位 2 的一个规范也引入了 DeviceOrientation 事件,当浏览器开始支持它们时,你可以用 JavaScript 把它们挂钩。在 http://dev.w3.org/geo/api/spec-source-orientation.html 的了解更多信息。

您有几种满足移动用户需求的解决方案。

另一个网站

最佳解决方案是为移动用户提供不同的网站。基于服务器端用户代理检测重定向您的用户(例如,重定向到[m.facebook.com](http://m.facebook.com)[touch.facebook.com](http://touch.facebook.com))允许您基于他们可能的分辨率和他们潜在的和缓慢的网络速度为他们建立完整的体验。不幸的是,除了最流行的设备,这是一件很难实现的事情。首先,该设备上的浏览器制造商(无论是专有的、Opera Mini、Mobile Safari、Firefox Mobile 还是其他浏览器)都付出了大量努力,以确保这些浏览器的用户能够享受良好的体验,即使是在小屏幕上。其次,可用的用户代理一直在变化,每天都有新的代理出现。 8

通过 iPhone 访问时,脸书会将你重定向到完整网站的触摸版(见图 7-5 )。使用accept-language HTTP 头向用户呈现正确的语言(设备正在使用的语言)也足够聪明。


8 Opera 其实伪装成 IE 很多年,以避免服务器端用户代理检测屏蔽浏览器内容。8.02 版改变了这种行为。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 7-5。*iPhone 上的脸书(上图,人像模式;下图,风景模式)。

你只能现实地瞄准其中最常见的。对于高度和宽度较小的设备,您可以使用媒体查询在显著位置提供移动网站的链接,但请记住,一些最新设备具有非常高的像素密度,可以轻松拥有比台式机更高分辨率的屏幕。如果您确实提供了一个单独的网站,您应该做几件事:

  • 提供移动网站的链接,以防您无法正确检测和重定向用户。
  • 也提供一个正规网站的链接。如果用户遵循它,记住他的偏好。
  • 尽可能以 CSS 精灵的形式提供你的图片。那些 HTTP 请求会对移动浏览器造成很大伤害。
  • 考虑文本链接而不是图像。
  • 考虑单列布局。
  • 瞄准垂直滚动而不是水平滚动。
  • 增加font-size值。
  • 避免依赖任何依赖鼠标的交互,比如:hover
  • 避免依赖任何依赖键盘的交互,比如:focus
  • 触摸屏设备使用:active状态,带按钮的设备使用:focus状态。
  • 避免浮动元素。
  • 请记住,用户可能会改变方向,并迎合横向和纵向。使用百分比和流体布局,而不是固定的像素大小,将有助于你做到这一点。
  • 使用 HTML5 输入类型,如“电话”和“电子邮件”,给设备提示如何帮助用户输入信息。不支持这些类型的设备将优雅地降级为文本。
  • 避免 CPU 密集型操作,例如卸载到 GPU(阅读第八章中的相关内容)、变换、过渡和动画。
  • 尽管使用accept-language头来检测语言是聪明的——使用地理位置 API 来检测位置——避免尝试使用 IP 地址来检测位置。这是出了名的不靠谱。

这种技术的缺点是它需要你维护两个网站。如果您可以使用相同的内容和模板系统来做到这一点,它可能会使实现起来不那么痛苦。这会给你的用户最好的体验。

Pizza Express 向移动设备提供其网站的精简版本,并增加了一些额外的功能,例如检测你的位置,并允许你在浏览器中拨打该餐厅的电话。这是一个很好的例子,说明你应该如何考虑移动设备:不只是打勾,而是给每个人你能给的最好的解决方案,不管是什么设备。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 7-6。*移动 Safari 上的“披萨快递”网站

使用媒体查询来定位移动设备

您也可以选择使用媒体查询来定位这些设备。请注意,这种方法对性能的影响是相当大的。不仅您的移动用户会因为下载您链接到的所有样式表而受到惩罚,不管应用的是什么样式表(通常,您覆盖的许多图像也是如此),而且您现有的用户群也将被迫下载为移动设备设计的样式表。这增加了贵公司的带宽成本,因为许多文件是毫无目的地提供的。

不支持媒体查询的旧设备将忽略它们或无法解析文件。这使得他们只能使用默认的行为,这通常是足够的(或者至少是被设计成足够的)。

安迪·克拉克在[www.stuffandnonsense.co.uk/blog/about/hardboiled_css3_media_queries/](http://www.stuffandnonsense.co.uk/blog/about/hardboiled_css3_media_queries/)提供了一系列针对各种移动设备的媒体查询,您可能会发现这很有用。但是,不要依赖他们只针对列出的设备。新设备每天都在出现,它们可能满足也可能不满足这些查询的要求。另外,不要同时使用所有这些查询;这将导致大量额外的 HTTP 请求,对移动设备体验的损害更大。如果您有真正的理由针对单个特定设备而不是具有特定功能的所有设备,例如,将 iOS 用户指向您为他们的平台开发的应用程序,请将此视为使用服务器端用户代理检测的唯一有效用途之一。只要有替代方法,就应该避免 UA 检测。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**你可以通过 tel:协议在网站内点击电话号码,就像这样:

<a href="tel:12345678901">(123) 456-7890</a>

对于已经在使用手机浏览你的网站的人来说,这是一个真正有用的功能。请记住使用国际号码,并以国家代码开始 href 中的号码。

开发一个应用程序

您也可以选择直接为您想要支持的设备构建应用程序。虽然这在某种程度上限制了您的受众,但是您可以利用设备特定的功能和 API,否则您将无法利用它们。你也可以在一个非常封闭的环境中测试你的网站,并且对你的结果更加自信。另一种选择是在应用程序的浏览器中显示您的网站,潜在地将它绑定到更多功能,并保持它作为一个网站直接可用,只在一个地方维护它。

与此类似的是原生网络应用,这是一种制作跨平台应用的可行方法,可能与网站具有相同的代码库,但可以访问原生 API。PhoneGap ( [www.phonegap.com/](http://www.phonegap.com/))包装你的代码,让它成为所有主要移动设备制造商的原生应用。批发应用社区(WAC― [www.wacapps.net](http://www.wacapps.net))和设备应用编程接口和政策工作组(DAP― [www.w3.org/2009/dap/](http://www.w3.org/2009/dap/))都不成熟,但是值得关注。

其他设备

您还应该考虑其他设备,这些设备可能是您的目标,也可能不是。内置网络浏览器的机顶盒、游戏机、智能电视中的原生浏览器、谷歌电视等等。其中每一个都有自己的功能和特点。我们建议尽一切可能进行测试。

搜索引擎优化(SEO)

搜索引擎蜘蛛(通过“抓取”所有可以找到的链接并将结果发送回搜索引擎来扫描您的网站的程序)本身可以被视为设备。他们有自己独特的用户代理,和自己独特的能力。搜索引擎优化(SEO)是确保这些蜘蛛有最好的机会到达你的所有内容,并且当它们到达时尽可能合适的做法。搜索引擎优化对大公司来说非常重要,因为它可以带来大量的流量,从而带来大量的销售额(或者不管你网站的目标是什么)。SEO 和可访问性一样,需要从一开始就考虑,并以一致和理智的方式实现。

讨论 SEO 技术的任何细节都应该有自己的书,但是我们将在这里提到一些高层次的观点。首先,我们将讨论白帽、灰帽和黑帽技术之间的区别:

  • 白帽子:这些技巧是出于善意和无私的目的。考虑到 SEO,这通常意味着编写好的内容并在适当的时候链接到它。搜索引擎喜欢这类内容;这有助于他们确保搜索结果与搜索词相符,这也正是他们的用户想要的。
  • 灰帽子:这些技术可以被认为是试图欺骗系统,但同样是在没有其他好的选择时出于善意使用的。一个例子是使用text-indent:-9999px;隐藏浏览器中的文本,但不隐藏屏幕阅读器中的文本(如前一章所讨论的)。这种技术也可以很容易地用于向页面填充网站作者想要排名的关键词,但对用户隐藏它们。搜索引擎通常会接受这种方法,而不会对有问题的网站造成不利影响(因为,目前,这是我们保持可访问性的最佳方法之一),但不会对受其影响的单词给予额外的权重。
  • 这些技术是彻头彻尾的欺骗系统的企图。例如,加载一个填充了您想要排序的关键字的页面,然后用 JavaScript 重定向(门口页面),使用服务器端用户代理检测向搜索引擎呈现一个与您的用户完全不同的页面(隐藏),将关键字放在白色背景上的白色文本中(不可见文本),等等。使用这些技术将会导致你的网站受到惩罚,并在搜索引擎结果中比其他情况下显示得更晚。在极端的例子中,这可能导致你的网站被禁止在搜索结果中出现。

许多人认为他们已经找到了新的和聪明的方法来欺骗搜索引擎。事实上,他们所做的一切都是在浪费时间,给用户更糟糕的体验,浪费本可以更好地用于改善网站的时间,推迟不可避免的事情。从长远来看,搜索引擎会发现你在做什么,并因此惩罚你。事实是,唯一一个确切知道什么会在搜索引擎的算法中排名靠前的是搜索引擎。即使这样,您的请求也可能被定向到许多具有不同算法版本的服务器中的一个,并且算法可能会经常改变。然而,还是有一些通用的指导方针。为了获得好的搜索引擎排名,重要的是

  • 拥有优质内容。
  • 确保你的内容是语义和有效的。
  • 确保你有良好的网站表现。

真的,这与制作高质量的网站是一致的,这也是我们希望你的目标。拥有好的内容是搜索引擎最重视的。使您的内容具有语义和有效性(包括使用微格式、公共类名和 id)有助于搜索引擎理解内容和您的意图,并在搜索结果页面上以更全面、更有吸引力的方式呈现内容。

根据搜索引擎的不同,蜘蛛作为设备有不同的功能。有的解析 JavaScript,有的不解析;有些解析 CSS,有些不解析。总的来说,搜索引擎现在理解并应用 CSS 到页面上。通常情况下,如果他们认为文本由于某种原因(灰色帽子)是不可见的,他们不会对你的页面的内容或其中的单词进行排名。如果您使用 JavaScript 来显示和隐藏信息(例如,在选项卡式的框中),这意味着在页面加载时不活动的选项卡将不会对其内容进行索引。如果搜索引擎支持 JavaScript,那么当抓取页面上的链接时,如果特定链接使内容可见,搜索引擎可能会选择抓取该内容,尽管它可能认为它不如最初显示的文本重要。

出于(至少)这个原因,你应该在禁用 JavaScript 的情况下,让你的网站以合理的方式显示。对于选项卡式框,将标记中的每个选项卡显示为一个标题,选项卡的内容直接位于其下是有意义的。当页面加载时, 9 您的 JavaScript 可以根据需要重新排列页面,并附加正确设置选项卡所需的类。正如我们之前提到的,这也是一个很好的可访问性方法;如果你使你的网站具有语义和可访问性,你可以免费获得一些搜索引擎优化。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**搜索引擎(几乎)永远不会提交表格。

搜索引擎越来越看重的另一件事是你网站的表现。如果你的网站下载速度很快并且通过了验证,你的排名会轻松提升。如果你一直遵循本书中的指导方针,希望你的 CSS 小而快,并且页面上没有太多的 HTTP 请求。使用内容交付网络(CDN——见第八章)也能提高你的速度,这是搜索引擎会欣赏的。


JavaScript 可以挂钩到一个“DOM 就绪”事件,该事件在图像加载之前、DOM 加载之后触发。此时对文档进行更改(而不是在下载完文档上的所有内容后触发的“load”事件)将避免非样式内容(FOUC)的闪现,这可能会非常令人不安。

总结

本章旨在向您介绍一些用户可能会使用的设备,以及如何最好地锁定他们。媒体类型和媒体查询通常是有用的,但它们并不是我们中的一些人所希望的灵丹妙药。在这些情况下,努力让你的网站易于阅读和浏览将会有所回报。

下一章关注的是 CSS 的性能,并打算教你 HTTP 请求和响应是如何工作的,缓存策略,以及如何最好地利用两者。

八、性能

CSS 文件通常是语法简单的小文件。看起来,就性能而言,几乎没有什么可做的,改进也是微不足道的。在很多情况下,这可能是真的。然而,当我们以更大的规模处理 CSS 时,我们的文件可能会变得更大,并且我们希望它们每天被提供数百万或数千万次,小的改进会产生大的差异——对用户和开发人员都是如此。以今天的标准来看,一千字节的数据量似乎很小,但是算一算,这些千字节很快就会增加到千兆字节的数据量,企业需要为此支付带宽费用。当我们考虑用户下载的有效负载和页面渲染的速度时,可以说每一点都很重要。

在关注性能时,我们必须考虑几个角度。从用户的角度来看,重要的是文件要小,可以很好地缓存(因此加载更快),并且文件是最新的。从浏览器的角度来看,我们希望我们的 CSS 尽可能高效,并且尽可能快地呈现内容(如果需要,响应交互或动画而重新呈现)。从企业的角度来看,我们希望尽可能多地从用户的缓存(主要是我们的服务器缓存,其次是我们的服务器缓存)提供服务,并将我们发送给用户(和从用户接收)的数量保持在最低水平,同时仍然确保用户拥有我们代码的最新版本。

在这一章中,我们将集中讨论如何从这三个不同的角度提高性能,并且您将了解到关于每一个方面的一些重要的事情。您将了解以下内容:

  • 文件大小问题和最佳实践
  • 拥有更少的 HTTP 请求比文件大小更重要
  • 缓存策略
  • 浏览器渲染和阻止

有效负载—担心文件大小

CSS 中的最佳实践要求我们考虑输入的字符数量以及它们的含义。每个字符都很重要。虽然现在高速互联网越来越普及,但作为一个高流量网站的 CSS 作者,你要担心的人口统计数据要比大多数其他公司多得多。因此,您的用户可能在拨号上网(出于选择或由于他们的位置),在接收信号差的地区使用移动设备,在远离您的站点(您的服务器)的国家,或这些情况的任何组合。预处理可能发生在许多级别,例如他们所在位置的 ISP、防火墙和路由器,或者数据路径上更高级别的防火墙和路由器。我们的数据尽快到达这些机器的主要问题之一是我们发送的数据量。当我们通过 TCP/IP(传输控制协议和互联网协议),互联网通常使用的网络协议发送数据时,我们的信息被分组为称为的包。在网络上有一个最大数据包大小的概念,或最大传输单元(MTU),在以太网上通常是 1500 字节。超过这个大小的数据包会导致性能损失(损失有多大取决于许多因素:MTU、数据包大小、网络速度等)。由于我们无法确定任何特定网络的 MTU 是多少——即使我们知道,也很难知道哪个数据包会超出限制——为了避免这种数据包边界,我们所能做的就是尽最大努力提供尽可能少的数量。

比方说,互联网用户比购物中心的用户更易变。决定一个网站太慢并且浏览到另一个网站提供了即时的满足感,而找到另一个出售平底锅的商店需要用户承诺离开当前的商店,找到另一个商店,找到产品,等等。用户对我们网站的好感是有限的,我们必须尽一切努力让用户满意,并朝着我们的商业目标前进——无论是购买产品或服务、浏览页面还是消费内容。

保持文件大小和速度不仅对我们的访问者有好处。这对我们的业务也有好处——我们提供的数据越少,我们产生的带宽成本就越少。此外,谷歌和其他搜索引擎关心我们的网页加载速度。网站的性能正迅速成为有效的 SEO 策略的一个重要因素。此外,Internet Explorer (IE)版本 7 和更低版本无法处理 288 KB 以上的 CSS 文件。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**对于超过 288 KB 的 CSS 文件,IE 7 及以下只会解析文件的前 288 KB。尽管可以将比这个大的文件分成多个文件(更多的请求会对性能产生影响),但显然最好首先保持我们的 CSS 较小。

那么,我们能做些什么来降低文件大小呢? 1

命名惯例

正如我们以前说过的,制定关于类和 id 如何命名的规则,并严格遵守这些规则是很重要的。除非有很好的理由不在您的组织中这样做,或者您的开发人员特别反对这样做,否则我们建议使用 camel case 命名您的类和 id,如下所示:

  • 主要内容
  • 英雄形象
  • 电子邮件地址

使用 camel case 应该有助于使我们的代码易于阅读,同时避免连字符、下划线或其他分隔符带来的额外字符。如果通过前缀给代码命名空间,尽量避免像这样冗长的前缀:

  • ourCompanyHeroImage
  • 我们公司的电子邮件地址

1 谷歌在[code.google.com/speed/pagespeed/docs/payload.html](http://code.google.com/speed/pagespeed/docs/payload.html)提供了一个让你的有效载荷最小化的指南。雅虎!提供自己的指南,以最大限度地提高您的网站在[developer.yahoo.com/performance/rules.html](http://developer.yahoo.com/performance/rules.html)的性能。

相反,考虑缩写或首字母缩略词;例如:

  • 赭石图像
  • ocEmailAddress

如果这些名字很难读,我们现在可以考虑使用一个分隔符,安全地知道我们的前缀仍然比其他的更小。

  • oc-HeroImage
  • oc 电子邮件地址

尽管我们希望我们的命名系统是语义化的,但我们也可以考虑将其缩写,只要它对开发人员来说仍然易于阅读:

  • 海洛因
  • 电子邮件地址

这种方法的缺点是单词有许多可接受的缩写,开发人员可能会发现命名不太直观,导致代码来回切换,或者命名技术不一致。我们建议(一如既往地)在您的组织内讨论什么是可接受的,以获得最佳结果,将该技术添加到您的 CSS 编码标准指南中,并严格执行。

文件名

在我们的代码中,文件名用于将文件链接在一起并引用它们。为了保持我们的文件非常小,将我们的文件命名为 a.css、b.css 等等是很有诱惑力的。事实上,只要文件使用正确的 mime 类型, 2 我们甚至可以将它们命名为**a . c .**或者仅仅是 a 。然而,实际上,我们认为这在易读性上造成了太多的成本,使得代码难以编写,文件难以定位。尽管文件名不应该太长,但是它们应该足够长,以便于理解。下面是一个在 CSS 中引用文件的例子:

input {background: urlimg/shadow background.gif");}

URL 中通常不需要双引号。因为文件名中有空格,所以将它们包含在本例中,这意味着需要引号,以便浏览器理解空格是文件名的一部分。但是,在命名文件时,我们建议您始终使用连字符而不是空格。我们建议这样做有几个原因:

  • Google 会将 URL 路径中的连字符视为空格,而不是其他字符。如果我们将我们的文件命名为“shadow background.gif”,Google 会将其理解为“shadow background.gif”。对用于展示的图片而不是内容(如背景)进行良好的 SEO 可能看起来没有必要,但谷歌图片搜索占据了惊人的流量,任何可能将用户带到我们网站的东西都是积极的事情。
  • 不使用空格意味着我们不需要 URL 值中的引号,节省了两个字符。
  • 使用空格意味着浏览器需要智能地处理它们,并在 URL(一个空格,URL 编码)中将它们更改为%20,而较旧的浏览器可能不支持这一点。当然,您可以将编码的空格直接放在 URL 中,但是这将导致每个实例中多两个字符。

2 A mime 类型是一个响应头,标识所提供文件的类型。CSS 文件的 mime 类型是“text/css”。

我们还建议您始终用小写字母命名文件,即使使用不区分大小写的 web 服务器/操作系统。这是一个容易遵循的规则,如果你以后决定更换网络服务器,它会使你的文件更容易移植。避免使用符号和不常见的字符还可以简化从一个服务器到另一个服务器的转换,并避免意外问题。尽管在文件名中使用点号已经成为版本控制的常见做法(如在 jquery-1.4.4.min.js 中),但是您应该避免将它们放在文件名的开头,因为基于 Linux 的操作系统会将其解释为隐藏文件。作为最佳实践,最好坚持使用拉丁字母数字字符、连字符和点。

文件夹结构

乍一看,文件夹结构似乎对文件大小没有什么影响。然而,这是不真实的。文件夹经常在整个 CSS 代码中被引用,无论是指向背景图像、字体、行为还是导入其他样式表。 3

通过引用文件夹来最小化字符消耗的最简单的方法是将所有文件保存在与 CSS 相同的目录中。这种技术在任何规模的网站中都会很快变得不可用,然而,和往常一样,我们应该在什么是好的实践和什么会让开发人员的生活成为噩梦之间寻求一个好的平衡。

下面是一个在 CSS 中引用文件的例子:

input {background: urlimg/shadow background.gif");}

我们的 URL 中的第一个字符是一个正斜杠,这意味着从 URL 的根开始。所以,对于一个位于[www.thedomain.com/css/style.css](http://www.thedomain.com/css/style.css)的 CSS 文件,这个 URL 会指向[www.thedomain.cimg/shadow%20background.gif](http://www.thedomain.cimg/shadow%20background.gif)。CSS 中的所有 URL 都是相对于包含 CSS 的文件所在的位置,所以假设我们将 CSS 更改为以下内容(省略了斜杠):

input {background: url("img/shadow background.gif");}

该 URL 将指向[www.thedomain.com/cimg/shadow%20background.gif](http://www.thedomain.com/cimg/shadow%20background.gif)

因为我们用 CSS 引用的任何文件主要是供 CSS 使用的,所以将它们保存在我们的 CSS 所在的同一个文件夹中的一个子文件夹中是有意义的。这使得我们的 CSS 更加可移植,因为我们可以复制或移动 CSS 文件夹,并且所有的文件路径和引用都将保持不变。它还为我们节省了 URL 中的正斜杠,或者…/,跳到一个文件夹,找到我们要找的文件。

通常,文件夹是以它们所代表的复数来命名的,例如:

  • 形象
  • 资产
  • 字体

3 行为通过 HTML 组件(HTC)文件实现,IE 版本 5 及以上支持。它们允许您通过 JavaScript 为元素分配特定的行为。因为它们引入了对 JavaScript 的依赖,并且只限于 IE 浏览器,所以我们不推荐你使用它们。

通过始终使用单数来代替,很容易节省字符并在我们的业务中实现这种命名技术。在文件夹名称中,使用缩写要安全得多,因为我们的整个文件夹结构(至少在高层)很可能是非常可预测的,并且从一个项目到另一个项目完全相同。这是另一件你应该添加到你的 CSS 编码标准指南中的事情:

  • 图片
  • 资产
  • 字体

我们的行最初是 58 字节,如下所示:

input {background: urlimg/shadow background.gif");}

它现在是 52 字节,如下所示:

input {background: url(img/shadow-background.gif);}

这减少了 10%。这可能看起来是一个小变化,但是如果我们的文档中有 100 行类似的代码,我们现在就可以节省 60 个字节,如果考虑到我们要响应的请求的数量,这意味着更多。

您还应该在文件名中随意使用常见且易于理解的缩写,这可以进一步减少我们的 CSS:

input {background: url(img/shadow-bg.gif);}

我们的资产文件夹只是一个总括文件夹,用于存放我们想要引用的不容易归入另一个分组的任何文件,如 Shockwave Flash (SWF)、多媒体 Adobe Flash 文件。记住所有这些,让我们来看一个文件夹命名策略的例子:

   /       css          img          font          asset       img       js       asset

如果您想一想我们引用这些文件的所有地方,这一切都转化为更小的文件、更快的下载和更少的带宽。

语法

为了易读性,我们在 CSS 语法中加入了许多不必要的部分。因为这些通常可以通过缩小工具被最小化(在这一章的后面会读到更多),如果你正在使用它们的话,在你写的时候把它们保存在你的 CSS 中是安全的。然而,了解这些及其含义是很重要的。

空白

CSS 实际上需要很少的空白字符。选择器各部分之间的间距是必要的,正如速记 CSS 中引用的项之间的间距,或者将多个部分作为其值的属性之间的间距一样。其他一切的存在纯粹是为了让我们作为开发人员更容易阅读和扫描我们的代码。

请注意以下代码:

`body
{
        font-family: arial, sans-serif;
        font-size: 16px;
        background-color: #f4f4f4;
}

.clear
{
        clear: both;
}`

从浏览器的角度来看,前面的代码与此完全相同: 4

body{font-family:arial,sans-serif;font-size:16px;background-color:#f4f4f4;}.clear{clear:both;}

虽然第二个例子更难阅读,但它大大节省了文件大小。其中一些值得简单解释一下:

  • 选择器和左大括号之间的空间是不必要的。
  • 属性名后面的冒号和值之间的空格是不必要的。
  • 逗号分隔值之间的空格是不必要的。
  • 右大括号后的回车是不必要的。
  • 空格在前!重要的都是不必要的(以及任何介于!而且重要)。

在所有这些情况下,您可以使用其他空白来代替空格,比如制表符。如你所见,这里有很多储蓄。我们不建议你用这种方式编写你的 CSS,因为这是不可能管理的(参见本章后面关于缩小你的代码的部分)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**当使用名称中带有空格的字体时,你应该根据 CSS 规范用引号(或者是双引号或者是单引号)将它们括起来。尽管许多浏览器对省略引号没有问题,但其他浏览器不能正确读取值。


4 虽然大多数浏览器不区分字体名称的大小写,但一些老的浏览器会区分。值得注意的是,Adobe Flex([www.adobe.com/products/flex/](http://www.adobe.com/products/flex/)—用于开发 Adobe Flash 和 Adobe AIR 应用程序的框架)存在字体名称大小写不正确的问题。

最后一个分号

最后一个属性之后和右大括号之前的分号是不必要的,在不影响可读性的情况下,很容易省略。

请注意以下几点:

.clear {clear:both;}

以及以下内容:

    .clear {clear:both}

就浏览器而言,这两行完全相同,并且为每个规则保存一个字符。

背景颜色

当指定背景颜色时,不要(以更正确的方式)陈述如下:

background-color: blue;

使用以下代码是安全的:

    background: blue;

这在每个实例中节省了六个字符。但是请注意,这种技术会覆盖这个速记属性中设置的其他属性,可能会产生意想不到的效果。

零和单位

因为无论使用什么单位,零都是零,所以在这种情况下没有必要使用单位。请注意这两个属性:

border-width: 0px; margin-top: 0em;

它们可以安全地写成如下形式:

    border-width: 0;     margin-top: 0;

这对页面的呈现没有任何影响。

取消边界

移除边框时,不要声明以下内容:

border-width: 0;

或以下内容:

    border: none;

相反,您可以安全地放置以下内容:

    border: 0;

同样,从浏览器的角度来看,这导致没有边框和更少的字符。

零和小数位

当使用从零开始的小数位时,可以安全地省略零。换句话说,不是陈述如下:

font-size: 0.12em;

安全的做法是改为使用以下内容:

    font-size: .12em;

这两个语句是完全等效的,并且在每个实例中都保存了一个字符,尽管对于一些开发人员来说它们可能更难阅读。这个动作是由大多数 CSS 缩小工具代表你采取的,所以如果你发现易读性受到影响并且你正在使用这些工具,保持零是安全的。

空白/填充速记

请注意以下 CSS:

margin-left: 10px; margin-right: 10px; margin-top: 10px; margin-bottom: 10px;

这也可以表示为(按上、右、下、左的顺序):

    margin: 10px 10px 10px 10px;

它也可以(因为所有四个值都相同)表示为:

    margin: 10px;

如果提供三个值,第一个代表顶部,第二个代表两个水平值,第三个代表底部。请注意以下 CSS:

padding-left: 5px; padding-right: 5px; padding-top: 20px; padding-bottom: 10px;

与此相同:

    padding: 20px 5px 10px;

此外,如果只为 margin 或 padding 属性提供两个值,它们分别代表垂直和水平值。请注意以下 CSS:

padding-left: 10px; padding-right: 10px; padding-top: 20px; padding-bottom: 20px;

这完全等同于:

    padding: 20px 10px;

也完全等同于这个:

    padding: 20px 10px 20px;

还有这个:

    padding: 20px 10px 20px 10px;

对于边框、边框半径、列表样式、字体以及其他许多属性,还有许多其他的速记属性,它们不在本书的讨论范围之内。值得花些时间研究和记录它们,并确保团队中的其他成员能够熟练使用它们。除了使你的 CSS 文件更小之外,它们还能使你的 CSS 更容易阅读和扫描。但是,您需要注意这种方法的注意事项,因为速记属性会覆盖组成它的所有子属性,可能会产生意想不到的后果。

颜色

每个浏览器都有一系列命名的颜色(在 HTML 3.0 的规范中定义),它可以理解这些颜色,并适当地呈现出来。其中包括以下: 5

  • 浅绿色
  • 黑色
  • 蓝色
  • 紫红色
  • 灰色
  • 绿色的
  • 石灰
  • 褐红色
  • 海军
  • 橄榄
  • 紫色
  • 红色
  • 水鸭
  • 白色
  • 黄色

5CSS 2.1 规范中也包含了颜色“橙色”,定义为#ffa500。背景色的 CSS1 规范中包含了颜色“透明”,它没有十六进制颜色的对等物。CSS2 使该属性适用于边框颜色,CSS3 将其定义为适用于任何接受颜色值的属性。

浏览器支持更多的颜色,但它们不是 W3C 标准的一部分([www.w3.org/TR/css3-color/#html4](http://www.w3.org/TR/css3-color/#html4))。然而,这些颜色中只有八种是“绝对”颜色,这意味着它们的红色、绿色和蓝色值都是零或 255。这些是如下: 6

  • 浅绿色(#00ffff)
  • 黑色(#000000)
  • 蓝色(#0000ff)
  • 紫红色(#ff00ff)
  • 石灰(#00ff00)
  • 红色(#ff0000)
  • 白色(#ffffff)
  • 黄色(#ffff00)

在这些颜色中,只有下列颜色不可解释:

  • 黑色(#000000)
  • 蓝色(#0000ff)
  • 红色(#ff0000)
  • 白色(#ffffff)
  • 黄色(#ffff00)

有趣的是,绝对的“绿色”被认为太亮而不能被描述为绿色,因此被命名为“石灰”;“绿色”作为关键词代表#008000,比绝对的绿色暗很多。

因此,我们建议在您的 CSS 中只使用这些。它们更容易阅读和理解,并且在许多情况下比它们的十六进制对应物的字符更短(除非你使用速记颜色(下面提到);在所有情况下,它们都比它们的 RGB 对应物短)。

但是,还可以实现进一步的节约。以十六进制表示的颜色(称为“十六进制颜色”)被指定为三个一组(意味着它们由三部分组成)。三元组的每个部分都是一个十六进制字节:一个有 256 个变量的值,在本例中从 0 (00)到 255 (ff)。三联体本身有 16,777,216 种变化。如果三联体的每个部分都由两个相同的字符组成,我们可以将它们缩短为一个。例如:

  • #112233 可以表示为#123
  • #00aaff 可以表示为#0af
  • #99aa00 可以表示为#9a0

使用命名颜色或十六进制颜色的决定将在您的组织内做出。在使用十六进制颜色的地方,使用简写版本是安全的。您可以决定在适当的情况下使用命名的颜色,因为它们更容易阅读,但是任何经验丰富的 CSS 开发人员都会习惯于看到#000 和#fff,因此在这种情况下坚持使用十六进制颜色没有问题。虽然 RGB 颜色是在 CSS2.1 规范中定义的,但 CSS3 扩展了这一点,以支持 RGBA 以及 HSL(色调、饱和度、亮度)和 HSLA(色调、饱和度、亮度、Alpha)。这些接受非十六进制值 RGB 值是介于 0 和 255 之间的整数,Alpha 是介于 0(透明)和 1(不透明)之间的数字。色调以度为单位(0 到 360),而饱和度和明度是百分比。虽然所有这些值都比十六进制颜色更冗长(并且增加了更多的文件大小),但是您可能会发现实现它们更有用。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**如果在 CSS 中使用 RGBA、HSL 或 HSLA 颜色定义,请确保为不支持它们的浏览器提供 RGB 或十六进制颜色回退。此外,如果您在 IE 中使用filter属性(可能是为了跨浏览器支持渐变、不透明等等),请注意该属性不支持速记颜色、RGB、RGBA、HSL 或 HSLA。一些较老的移动浏览器对这样的简写颜色有问题。然而,这些浏览器几乎已经消失了,即使是流量最高的网站,我们也不建议关注这一人群。

作为使用这些语法方法来节省文件大小的最后一个例子,请参见下面的 CSS,重量为 146 字节:

.our-company-main-content {    background-image: url("../img/shadow background.gif");    border: none;    margin-top: 0px;    color: #aabbcc; }

这可以很容易地缩短到 110 字节:

.oc-mainContent {    background-image: url(img/shadow-background.gif);    border: 0;    margin-top: 0;    color: #abc }

可读性没有损失,但是我们节省了 36 个字节,减少了 25%——甚至在缩小文件之前。

最小化图像尺寸

了解什么样的图像类型适合什么样的情况以及如何缩小图像大小至关重要。在接下来的部分中,我们将讨论您将使用的三种主要类型的图像。

GIF(图形交换格式)

gif 最多支持 256 种颜色,并支持透明,但不支持 alpha 透明。这意味着,通过使用我们可用的 256 种颜色中的一种,我们可以将像素设置为完全透明或完全不透明(不透明),但不能介于两者之间(半透明)。它们通常适用于按钮图像、具有硬边的项目以及颜色准确性非常重要的图像。gif 是一种无损压缩格式,这意味着只要您使用 256 色或更少的颜色(包括透明度),图像质量就不会下降。当保存 gif 时,使用你的图像编辑器来确保你最小化使用的颜色数量,并且如果你不需要的话关闭透明度。gif 可以包含多个帧和信息,以在这些帧之间进行动画制作,并在这些帧之间循环播放简单的动画(这会显著增加文件大小,应谨慎使用)。

联合图像专家组

JPEG 压缩是有损的,这意味着图像压缩得越多,信息就丢失得越多。您的图像编辑器应该包括一个滑块或类似的工具,用于在导出图像时调整压缩量。当您降低质量(提高压缩率)时,文件大小会减小,但图像中会出现伪像。由设计者决定适当的和视觉上可接受的压缩水平,通常 80%到 90%是最好的折衷,并且应该产生不明显的伪像。JPEGs 支持 1600 万种颜色,最适合照片或多种颜色的复杂图像。JPEGs 完全不支持透明。

PNG(便携式网络图形)

PNG 是一种无损格式,通常具有比 GIF 文件更好的压缩率。它们可以保存为不同的颜色深度,这意味着它们可以支持适合当前图像的任意多种颜色。它们还支持 alpha 透明,这意味着像素可以是半透明的。png 通常适合作为 gif 的替代品,尽管动画 png 还没有得到广泛的支持。保存 png 时,将颜色深度设置为适合图像的值是很重要的。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**虽然 IE 6 不支持 alpha 透明,但是有很多黑客可以使用 JavaScript、行为文件、过滤器以及它们的混合来解决这个问题。我们推荐的方法是 Fireworks hack,或 8 位 PNG hack。如果您将 Adobe Fireworks 中具有 alpha 透明度的 PNG 保存为 8 位(这实际上是软件中的一个错误),结果是它可以在所有浏览器中正常显示,但 IE 6 除外,它可以使任何半透明像素完全透明。这通常是一个可以接受的折衷方案,只要你的设计者考虑到这个约束。

您的图像编辑器应该有工具来帮助您最小化这些文件的文件大小,并查看各种文件格式、压缩率和颜色深度的差异。然而,即使在这之后,在您的图像文件中也可能有无关的信息。注释、元数据和不必要的颜色配置文件可能会留在文件中。有许多单独的工具可以解决这些问题,但 ImageOptim(可在[imageoptim.pornel.net/](http://imageoptim.pornel.net/)获得)是一个面向 OS X 的免费开源应用程序,它结合了所有这些工具。它通常可以节省 30%到 35%的文件大小。在您的构建过程中实现这样的工具是一件值得做的事情,应该认真考虑。可以使用各种 Windows 和 Linux 替代工具(尽管功能不太全面),但是在构建过程中使用多个命令行实用工具应该能够获得相同的结果。

缩小

使用本章到目前为止概述的技术,编写你自己的 CSS 缩小脚本并不困难,它可以代表你自动完成所有的工作。然而,在这个过程中有很多事情会让你措手不及。幸运的是,已经有方法可以帮你做到这一点。其中最常见的是 YUI 压缩机。

YUI 压缩器是一个基于 Java 的工具,用于缩小 JavaScript 和 CSS 文件,这些文件可以在[github.com/yui/yuicompressor/](http://github.com/yui/yuicompressor/)下载。它代表我们自动执行任务,照顾到我们在本章中已经讨论过的最小化文件大小的许多方法,这意味着我们可以在开发代码中忽略它们,但仍然可以在生产代码中获得好处。一旦安装,语法非常容易使用。在命令提示符下,语法如下(其中 x.y.z .是版本号):

java -jar yuicompressor-x.y.z.jar [options] [input file]

适用于 CSS 的潜在选项有:

--line-break

这个选项允许我们在一个特定的列之后将 CSS 分成几行(在这个例子中,表示一行中的一个特定字符)。例如,将此项设置为 1000 将在每行的第一千个字符后插入一个换行符。在很多情况下你可能需要这个选项,特别是在调试的时候(你可以在第十章中读到更多关于调试的内容)。将此项设置为 0 将在每个规则后插入一个换行符。

--type

该选项允许我们指定输入文件的格式。只有当我们的文件后缀不是。css(或者。js),在这种情况下,对于我们的 css 文件,我们应该总是将它设置为 CSS。

--charset

这个选项允许我们指定输入文件的字符集。如果不提供,将使用运行 YUI 压缩程序的系统的默认字符集编码。虽然在大多数情况下没有必要,但如果您使用 UTF-8 字符(例如,在内容属性中),则应该设置此选项。

-o

此选项指定我们要输出到的文件的名称。

以下是一个命令示例:

java -jar yuicompressor-x.y.z.jar style.css –o style.min.css

YUI 压缩程序对 CSS 文件执行以下功能:

  • 一条条评论 7
  • 删除不必要的空白
  • 删除每个规则的最后一个分号
  • 删除任何多余的分号
  • 删除任何没有属性的规则
  • 移除任何零值上的单位
  • 删除任何小数位以零开始的值的前导零
  • 在边距、填充和背景位置将相似的属性合并为一个
  • 将任何 RGB 颜色转换成十六进制颜色 8
  • 删除无关字符集 9
  • 使用 filter 属性将 alpha 不透明度语句缩短为 IE4 样式 10

如果出于某种原因,你想保留特定的评论(也许是出于版权或许可的目的),你可以在评论的开头使用感叹号,就像这样:

/*! This comment is far too important to be removed: */

正如本章后面提到的,当使用 CSS 过渡时,在一些浏览器中使用十六进制颜色而不是 RGB 颜色会有性能损失,所以意识到这一点尤为重要。

如果您使用的是 YUI Compressor,您可以放心,因为所有这些操作都将被执行,而且,如果您愿意,也不用担心它们会出现在您的未统一代码中。

如果您使用过任何黑客攻击(其中许多在第三章中有详细介绍),了解 YUI 压缩机容忍其中许多攻击并且不会试图缩小或破坏它们是很有用的。公认的方法包括:

  • 下划线黑客
  • 明星黑客
  • 子选择器黑客
  • 注释的反斜杠 hack
  • 盒子模型黑客

如果您不希望使用 YUI 压缩器的 Java 实现,可以在[www.phpied.com/cssmin-js/](http://www.phpied.com/cssmin-js/)找到一个 JavaScript 端口。还有许多缩小 CSS 的其他选项,包括:

  • [www.csscompressor.com/](http://www.csscompressor.com/)
  • [www.cleancss.com/](http://www.cleancss.com/)
  • [www.cssdrive.com/index.php/main/csscompressor/](http://www.cssdrive.com/index.php/main/csscompressor/)

9 可以用如下命令定义 CSS 中的字符集:

@charset "utf-8";

任何 CSS 文件只能包含一个@charset 语句。

10 滤镜属性(Internet Explorer 专有)允许你设置各种视觉效果。要设置不透明度,建议的语法是:

selector { -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=65)"; filter: progid:DXImageTransform.Microsoft.Alpha(Opacity=65); }

filter 属性适用于 IE 8 和更低版本。–ms-filter 属性适用于 IE 9 及更高版本。YUI 压缩器将这些简化为它们的速记等价物,如下所示:

selector { -ms-filter:"alpha(opacity=65)"; filter:alpha(opacity=65); }

由于 YUI 压缩机的广泛使用,它是我们推荐的产品。使用它并为社区做出贡献的开发人员的数量意味着它是一个非常健壮且经过良好测试的解决方案。无论你做什么决定,我们都建议你对 CSS 代码使用一个缩小工具,尽管这意味着调试(你可以在第十章阅读更多关于调试的内容)是可以克服的。

压缩

在我们尽可能地将文件变小之后,我们仍然可以使用一些技巧来使从服务器传输给用户的数据变得更小。其中之一是压缩。文件的压缩涉及到使用算法来存储重复的数据,并且只表示该数据一次而不是多次。有两种主要的压缩方式:无损压缩和有损压缩。有损压缩意味着在压缩过程中会丢失一些信息,JPEG 就是有损压缩格式的一个例子。我们对 JPEG 图像压缩得越多,出现的伪像就越多,离原始图像就越远。这对于图像格式来说是可以接受的,因为我们试图传达的内容并没有完全丢失。然而,对于 CSS 文件,任何引入数据的工件都会破坏我们的语法,使我们的文件变得不可预测和无用。无损格式,如 zip、tar 和 rar 文件,是我们需要的每个字符都很重要的数据。

HTTP 1.1——用于交付网页的协议——在其规范中支持三种压缩方法:gzip (GNU zip)、deflate 和 compress。浏览器和服务器对 deflate 和 compress 的吸收比 gzip 慢。所有现代浏览器和服务器都支持数据的 gzip 压缩,因此,它已经成为在互联网上压缩数据的行业标准技术。 11

下面显示了从浏览器向服务器发出请求以及服务器做出响应的示例流程:

  • The browser makes a request to the server, and includes headers similar to these: GET /index.html HTTP/1.1 Host: www.domain.com Accept-Encoding: gzip User-Agent: Firefox/3.6

    这些行依次表示以下含义:

    • 浏览器正在使用 HTTP 1.1 协议请求文件/index.html。
    • 浏览器指定它想要文件的域。
    • 浏览器表明它支持 gzip 编码。
    • 浏览器将自己标识为 Firefox 3.6。
  • 服务器找到该文件并将其读入内存。

  • 如果浏览器表明它支持 gzip 压缩(在本例中就是这样),服务器就会压缩文件。

  • The server returns the data, with headers similar to the following: HTTP/1.1 200 OK Server: Apache Content-Type: text/html Content-Encoding: gzip Content-Length: 12345

    这些行依次表示以下含义:

    • 服务器确认它正在使用 HTTP 1.1 作为协议,并发送一个 200 状态码来表示一切正常。
    • 服务器将自己标识为 Apache。
    • 服务器将响应的内容识别为 HTML。
    • 服务器通知浏览器数据是用 gzip 压缩的。
    • 服务器指定它返回的数据的大小,以便浏览器可以显示一个进度条,并确定何时收到所有数据。
  • 浏览器解压缩数据,并读取它。


11 你应该知道,使用 gzip 时,由于压缩算法需要考虑单个字符,因此对于较小的字符集,压缩率会更高。提高性能的一个简单方法是尽可能使用小写字符,尽管这不如减少字符重要。

使用最新的 web 服务器,在服务器上实现 gzip 压缩很容易,您将在接下来的几节中看到这一点。

Apache

mod_deflate 是 Apache 用来压缩文件的模块。它包含在 Apache 2.0.x 源代码包中。安装 Apache 后,编辑适当的。conf 文件,并确保以下行存在:

LoadModule deflate_module modules/mod_deflate.so

一些老版本的浏览器在压缩特定类型的数据时会出现问题。定位它们很容易,这样做不会影响性能。Netscape 4.x 除了压缩 HTML 文件之外,还存在其他问题,因此我们应该添加以下内容:

BrowserMatch ^Mozilla/4 gzip-only-text/html

Netscape 4 的特定版本甚至有更糟糕的问题,所以我们可以完全禁用 gzip 压缩:

BrowserMatch ^Mozilla/4\.0[678] no-gzip

虽然 IE 有一个用户代理,也是以“Mozilla/4”开头的,但它对 gzip 压缩没有问题,所以我们可以取消那个浏览器的那些命令: 12

BrowserMatch \bMSI[E] !no-gzip !gzip-only-text/html


12 Apache 2.0.48 在 mod_setenvif 中有一个 bug,这就是为什么正则表达式在这个例子中看起来有点怪异。

我们不想压缩图像,因为它们已经有了自己的压缩方式。压缩这些文件只会给服务器和浏览器带来不必要的更大处理负载,而不会带来文件大小的优势。我们可以用这一行来停止:

SetEnvIfNoCase Request_URI \ \.(?:gif|jpe?g|png)$ no-gzip dont-vary

某些版本的 Adobe Flash 在通过 gzip 压缩时存在问题,因此,如果您发现 SWF 文件存在问题,可以将该行修改如下:

SetEnvIfNoCase Request_URI \ \.(?:gif|jpe?g|png|swf)$ no-gzip dont-vary

最后,一些代理和缓存服务器自己决定压缩什么和不压缩什么,并且可能通过这种配置提供错误的内容。下面一行告诉这些服务器执行我们设置的规则:

Header append Vary User-Agent env=!dont-vary

现在,任何对 CSS 文件(以及其他文件)的请求都将被支持它的浏览器压缩。

微软 IIS(互联网信息服务)

在 IIE 启用 gzip 甚至更容易。

对于 IIS 6(参见图 8-1 ),右键单击网站(或只是您想要启用压缩的网站)并选择属性。单击服务选项卡。您可以选择对所有静态文件(图像、CSS、字体、JavaScript、swf 等)和/或所有应用程序(动态)文件启用压缩。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-1。 在 IIS 6 中启用 HTTP 压缩

对于 IIS 7,默认情况下对静态文件启用 gzip 压缩。对于动态(应用程序)文件,您可以使用以下命令启用它:

appcmd set config -section:urlCompression /doDynamicCompression:true

对于 IIS 之前的版本,文件压缩是不可靠的,我们不建议在生产环境中启用它。对于版本 6,您可以使用以下两个命令指定要压缩的静态文件类型:

cscript adsutil.vbs SET W3SVC/Filters/Compression/Deflate/HcFileExtensions "htm html txt css" cscript adsutil.vbs SET W3SVC/Filters/Compression/gzip/HcFileExtensions "htm html txt css"

最后一个参数(用引号括起来)在一个空格分隔的列表中列出了要压缩的静态文件。

对于 IIS 7,有一个名为 applicationHost.config 的文件使用 XML 语法,该文件通常位于 C:\ Windows \ System32 \ inetsrv \ config \ application host . config 中,该文件列出了应该压缩的动态和静态文件的 mime 类型。静态文件在一个名为<staticTypes>的标签中。以下是该标签的一些示例内容:

<staticTypes>     <add mimeType="text/*" enabled="true" />     <add mimeType="message/*" enabled="true" />     <add mimeType="application/javascript" enabled="true" />     <add mimeType="*/*" enabled="false" />   </staticTypes>

这个标签支持 mime 类型中的通配符,所以条目“text/*”将告诉 IIS 7 压缩 css(对于 CSS,mime 类型是“text/css”)。

几乎所有的现代网络服务器都支持 gzip 压缩。如果默认情况下未启用,请查阅 web 服务器的文档以了解如何启用它。

使用像 Firebug 这样的检查工具(或者许多其他工具中的一个,参见第十章),您可以检查响应和请求的发生,以确保 gzip 压缩正在发生,并了解您的文件压缩得如何。

下面是一个发生在特定站点的请求/响应过程的例子,在这个实例中:[stackoverflow.com](http://stackoverflow.com)

首先是请求(参见图 8-2)。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-2。*请求标题

然后是响应(见图 8-3 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-3。*响应头

从这些可以看出,我们最初的 Accept-Encoding 头声明我们支持 gzip(以及 deflate)。然后可以看到响应是用 gzip 编码的(通过检查 Content-Encoding 头)。这证明 gzip 在服务器上工作。您也可以查看该部分上方的行,以便对请求的整体结果一目了然(参见图 8-4 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-4。 基本 HTTP 请求详情

我们可以从中看到请求的确切文件、从服务器返回的 HTTP 状态代码、请求文件的域、文件的大小以及加载文件所花费的时间(分解成更小的部分)。我们将在第十章中更详细地讨论 Firebug。

使用雅虎的另一个火狐插件!名为 YSlow,还可以看到文件被 gzipped 前的大小(见图 8-5):13

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-5。*显示压缩前后文件大小的 YSlow】

这向我们展示了文件在被 gzipped 之前是 31.7 KB,之后是 7.8 KB。这大约节省了 75 %!显而易见,这种技术对于降低文件大小来说是非常重要且容易实现的。

YSlow 的一个更全面的替代产品是网页测试([www.webpagetest.org/](http://www.webpagetest.org/)),它是由 AOL 在 2008 年开源之前开发的。它允许你专门针对 IE 的所有版本运行测试,甚至可以运行多次并显示平均值,以获得更准确的结果。

内容分发网络(cdn)和域

浏览器对它们可以同时拥有的连接数进行了限制(通常在 35 左右)。这是一件明智的事情;如果我们试图同时请求 200 个文件,那么每个连接的速度都会急剧下降。不仅如此,它们还对特定域的同时连接数进行了限制。最近这个限制有所松动,有些浏览器让你修改这个值,但是默认: 14

  • IE 6 和 ie7 支持每个域 2 个同时连接。
  • IE 8 支持每个域 6 个同时连接。
  • IE 9 支持每个域 2 个同时连接(在撰写本文时——推测当 IE 9 退出测试版时,这个限制会增加)。
  • Firefox 2 支持每个域 2 个同时连接。
  • Firefox 3 支持每个域 6 个同时连接。
  • Firefox 4 支持每个域 6 个同时连接。
  • Safari 3、4 和 5 支持每个域 4 个同时连接。
  • Chrome 6、7、8 和 9 支持每个域 6 个同时连接。
  • Opera 9 支持每个域 4 个同时连接。
  • Opera 10 支持每个域 8 个同时连接。

YSlow 是一个非常有用的工具,可以准确定位是什么让你的网站变慢了,更重要的是,它给出了很多可以修复它们的提示。可以在[developer.yahoo.com/yslow/](http://developer.yahoo.com/yslow/)下载。

有趣的是,随着浏览器的更新,连接不仅在增加,还在上下波动。

大多数当前的浏览器支持每个域至少 4 个同时连接。这意味着,如果我们有一个 HTML 文件、一个 CSS 文件、一个 JavaScript 文件、4 个背景图像和 4 个内嵌图像,我们已经有 11 个需要连接的单独项目,其中最多有 9 个可以排在其他连接之后。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示: Browserscope ( [www.browserscope.org/](http://www.browserscope.org/))是比较这些浏览器功能的一个极好的资源。

此外,当发送文件请求时,我们到目前为止显示的标题并不是随请求一起发送的唯一标题。另一个值得注意的头是“Cookie”头。Cookies 是用于在用户机器上存储持久数据和会话信息的信息片段,因此当从一页浏览到另一页时,数据可以保存在本地并被服务器引用。可以在 cookie 上设置过期日期,因此,当浏览器关闭并在以后重新打开时,过期日期仍然有效。cookie 与一个域相关联,任何来自该域的资源请求都会在请求中包含 cookie。再读一遍——“任何要求。”当我们从服务器请求动态内容时,cookie 对于这些页面的正确运行至关重要,但是 cookie 也会随着对静态文件(如图像或 CSS 文件)的请求一起发送。

在某些网站上,这些 cookies 的大小可能不小。单个 cookie 的最大大小为 4096 字节(4 KB),针对特定的域可以设置最大大小为 20 字节,因此对于给定的域,cookie 的总大小可能为 80 KB,不包括发送它们所需的分隔符。尽管这是一个极端的情况,但是如果我们在一个页面的每个图像请求中发送 80 KB 的数据,那么下载所有这些图像的时间将会大大增加。

解决这些问题的方法很简单。如果我们将主要/动态内容和静态内容领域分开,会发生两件大事:

  • 我们的 cookies 不再发送到我们的静态内容。这导致一个更小的请求,这是一个好消息,没有任何影响,因为数据是没有价值的。
  • 我们的连接限制适用于单个域,而不是一个域,使我们的连接限制增加了一倍。

实现这一点并不意味着对我们的架构进行大规模的反思。如果我们有一个初始域名[somedomain.com](http://somedomain.com),我们可以继续从[somedomain.com](http://somedomain.com)[www.somedomain.com](http://www.somedomain.com)提供我们的主页和动态内容。然后,我们可以创建[assets.somedomain.com](http://assets.somedomain.com),并将其指向带有域名服务器(DNS)条目的相同位置。我们的文件夹结构不需要做任何改变,我们已经提高了我们的性能。这使我们的连接限制增加了一倍,但是我们可以做得更好。我们可以遵循一个简单的规则,让我们在这些子域中保持一致,但仍然有很多子域。如果我们考虑我们引用的文件类型的文件后缀,我们可以在我们的域名中使用它们。例如:

  • [www.somedomain.com](http://www.somedomain.com)[somedomain.com](http://somedomain.com)为我们的主要和动态内容
  • [gif.somedomain.com](http://gif.somedomain.com)为 gif 图片
  • [jpg.somedomain.com](http://jpg.somedomain.com)对于 jpeg 图像
  • [png.somedomain.com](http://png.somedomain.com)对于 png 图像
  • [swf.somedomain.com](http://swf.somedomain.com)对于 swf 文件
  • [css.somedomain.com](http://css.somedomain.com)对于 css 文件
  • [js.somedomain.com](http://js.somedomain.com)对于 js 文件

如果我们始终如一地实现这一点,我们的 CSS 最终会有如下规则:

input {background: url(http://css.somedomain.com/css/img/shadow-background.gif);}

这完全违背了我们最小化文件大小的努力。相反,我们应该将域http://css.somedomain.com直接指向我们的 CSS 文件夹,并继续相对地引用这个文件夹中的文件。这是最小化 CSS 文件大小和拥有多个域之间的一个很好的折衷。我们可以引用我们的 CSS:

<link rel="stylesheet" href="http://css.somedomain.com/style.css" type="text/css" />

并继续像这样引用我们的图像:

input {background: url(img/shadow-background.gif);}

虽然上面的例子可能有些夸张,而且实现这么多子域也不可行,但仅仅一个子域就能带来值得的性能和带宽成本的改善。事实上,拥有多个域会对性能产生影响。每次额外的 DNS 查找都会导致性能下降,因此必须权衡这些好处和 DNS 查找成本。最多四个额外的域被认为是合理的数量。

使用子域的替代方法是使用内容分发网络(CDN)。

正如你将在本章后面看到的,在用户的计算机和他们连接的服务器之间有许多点;每一个都有性能影响。

CDN 将您的静态内容分布在许多服务器上,并总是试图从最接近用户位置的 CDN 响应用户。这可以极大地提高性能。实现 CDN 的困难在不同的选择中有很大的不同,但是使用它们的好处是不可否认的。虽然有几个免费的 cdn 可用,但对于高流量的网站,你需要使用商业产品来处理你所关心的那种带宽。一些比较知名的商业 CDN 产品如下:

  • 赤眉——www.akamai.com/
  • 【亚马逊云锋】——http://aws.amazon.com/cloudfront/
  • 微软 Windows Azure——www.microsoft.com/windowsazure/

对于一个高性能的网站来说,无论你是选择在本地用子域还是通过 CDN 来提供你的静态 CSS 文件,这些都是必须考虑的事情。

拥有更少的请求比文件大小更重要

对于一个规模合理的网站来说,你处理的不仅仅是一个 CSS 文件。以这种方式工作是荒谬的,原因有几个:

  • 多个开发人员可能会同时处理同一个文件,伴随着这种方法的所有负面问题,例如冲突和合并问题。
  • 当你只关心其中的一小部分时,扫描和阅读这种大小的文件是很困难的。
  • 只为一个网站提供一个文件不利于用户。在主页上下载一个大文件会使页面呈现非常缓慢,因为除了站点的特定区域之外,可能不需要许多规则或选择器。

更有意义的做法是将 CSS 分割开来,分别处理各个小部分。为了举例,让我们列出几个我们可能会用到的文件:

  • reset . css-重设. CSS
  • global.css
  • home.css
  • log in . CSS-登入

reset.css 和 global.css 文件是我们希望在每个页面上使用的文件,而 home.css 只用于主页,login.css 只用于登录页面。如果我们像这样保存文件,链接到 CSS 文件的主页代码将如下所示:

`

`

这有几个含义。最明显的是我们的主页 HTML 比它需要的要大一点。接下来,我们必须创建三个单独的连接来获取每个文件。除了最大连接限制,这对性能还有另一个更极端的影响。

事实证明,文件大小和连接限制并不是导致文件加载缓慢的唯一原因。让我们再来看看 Firebug 中向我们展示文件下载时间的部分(见图 8-6 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-6。 一个 HTTP 请求/响应的阶段

您可以在这里看到,数据的接收并不是这个特定项的加载时间的最大原因。让我们把它分成几部分,就像我们在处理[css.somedomain.com/home.css](http://css.somedomain.com/home.css)一样。

域名服务器(DNS)查找

DNS 是一种将域名映射到 IP 地址的目录。在这一点上,浏览器首先试图找出css.somedomain.com指向哪里,因此它联系一个 DNS 服务器(特定于用户的网络设置),向该服务器请求该域的 IP 地址,并接收一个带有答案的消息。如果服务器已经知道结果,它会立即从缓存中返回结果;否则,它会询问更上游的其他 DNS 服务器。nslookup 工具(见图 8-7 )让我们很容易看到这个过程的结果。 15

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-7。*nslookup 工具


15 每个 DNS 条目都有一个生存时间(TTL)。这是 DNS 服务器应该缓存结果的时间。它通常被设置为一天或更长时间,这就是为什么对 DNS 条目的更改可能需要一段时间才能传播到所有服务器。

幸运的是,DNS 是非常可缓存的,通常非常快。一旦用户的浏览器知道了特定域的 IP 地址,它(通常)在短期内不会再询问。但是,第一次从服务器获得响应确实会导致性能下降。 16

连接

此时,浏览器实际上正在与服务器建立连接。因为它有直接的 IP 地址,所以有理由假设这个过程应该非常快,并且浏览器可以直接连接到远程机器。不幸的是,事实并非如此。在用户机器和服务器之间有几个路由器,数据包需要通过它们来建立连接。traceroute 命令让我们看到了这一点的证据(见图 8-8 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 8-8。*traceroute 命令

正如您所看到的,在这个特定的例子中,在用户的计算机和服务器之间有 11 个独立的路由器。这将导致显著的性能下降,并且对服务器的每个请求都会发生这种情况。

发送

此时,连接已经建立,用户的浏览器正在向服务器发送数据。这些数据包括请求头和为该特定域本地存储的任何 cookies。对于 CSS 文件,我们已经讨论了如何最好地减少这种延迟。在前面显示的例子中,有很少的数据要发送,这实际上不会导致延迟。

等待

服务器现在已经收到了用户的请求并正在处理它。总会发生一定量的处理,即使它只是涉及远程服务器读取文件并将其返回,或者缓存服务器从内存中读取数据。如果请求的文件是动态的,并且需要特定于用户的处理(不能远程缓存),性能影响会更大。


一些浏览器,比如 Firefox,有一个他们引用的内部 DNS 缓存。通过这个缓存需要用户重新启动浏览器或使用扩展。

接收

最后,数据从服务器返回,浏览器接收它。在这种情况下,返回的数据非常小,网络速度非常快,因此延迟可以忽略不计。

从这个过程中我们可以看到,即使我们最大限度地减少了发送和接收的数据,仍然有其他因素在很大程度上影响性能。每次连接到服务器都会产生开销,而且这种开销通常比文件大小大幅增加所带来的影响更大。从中吸取的教训是,尽管最小化文件大小是最佳实践,当然也是值得努力的事情,但是请求越少对性能的影响就越大。 17

串联

下面显示的初始示例显然不是形成 CSS 文件的最佳方式:

`

`

让我们假设在这个实例中,我们的 reset.css 文件的大小是 2 KB,我们的 global.css 文件的大小是 4 KB,我们的 home.css 文件的大小是 6 KB。将这些文件连接成一个文件是减少服务器连接数量的一种明显的方法。由于 reset.css 和 global.css 在每个页面上都是必需的,因此我们应该将它们合并成一个页面,并将 home.css 分开,以防止用户不必要地下载重复的内容。实际上,对于这些小尺寸来说,这不是事实。如果我们将这三个文件连接成一个文件,我们将获得这个页面的最佳性能。但是,当用户浏览我们的登录页面时,我们希望包含这些文件:

`

****`

将这三个文件连接成一个文件似乎是违反直觉的——用户将像以前一样下载完全相同的 6 KB 文件(reset.css 和 global.css)。这似乎是重复劳动。但实际上,额外的 6 KB 下载不太可能像额外的 HTTP 请求的成本那样大(对大多数用户来说)。

这在高流量站点上如何工作取决于全局文件和特定文件的大小,并且需要测试以确保获得最佳结果。

我们还可以考虑将所有四个文件连接在一起。这是一个较大的初始点击,但是将在第一页之后被缓存,并且不会被服务器请求用于使用相同 CSS 的其他页面。

将这些文件连接在一起是一个简单的过程,但最好在构建过程中处理(如第九章中所述)。这使我们能够处理未压缩的单个文件,以获得最大的可读性,同时还能确保投入生产的代码尽可能的小和高效。


这就是为什么@import 指令应该少用的一个很好的理由;每一个额外的要求都会妨碍我们的表现。媒体选择器可以和 link 标签一起使用来引用单个文件,或者内嵌在 CSS 文件中,使我们的选择器更加具体。您应该使用哪一个取决于您有多少规则需要如此具体。HTTPS 连接招致更大的开销。

CSS spreads

CSS 精灵是提高性能的一个很好的方法,可以让页面感觉更快更灵敏。通常在我们的页面中,一个图形按钮可能有几种状态: 18

  • 正常—按钮的正常状态
  • 悬停(Hover)——显示的图像向用户表明他们的鼠标当前在可点击的东西上
  • 点击—显示的图像,向用户表明他们已经成功地与按钮交互
  • 禁用—显示的图像向用户表明该按钮不会对交互做出反应

为了达到这种效果,通常使用四个独立的图像。为了便于演示,我们来看看悬停状态:

`a {
        background: transparent url(img/button.gif) no-repeat 0 0;
        height:40px;
        width:120px;
        display:block;
}

a:hover {
        background: transparent url(img/button-hover.gif) no-repeat 0 0;
}`

浏览器选择如何处理悬停图像因浏览器而异。有些人可能会立即将其加载到缓存中,这样当用户悬停在锚点上时,加载图像就不会有延迟。其他人可能会选择等到用户悬停在元素上时再向服务器请求图像。第一个例子导致了对服务器的额外请求的性能成本。第二种方法会产生这种开销,但只是在加载之后,影响较小的时候。然而,在向用户显示该图像时存在延迟,给用户带来更差的体验。

这个问题是有解决办法的。CSS 精灵允许我们将多个图像堆叠成一个(见图 8-9 )。


不要依赖悬停状态来实现页面上的任何功能。这样做就是创建设备依赖关系;用户可能由于残疾而无法使用鼠标,或者可能正在使用对悬停状态没有概念的触摸屏界面。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 8-9。 一个精灵形象的例子

这张图片包括我们的常规按钮,在它的正下方是我们的鼠标悬停状态。通过使用背景位置,我们可以在 CSS 中的两个地方引用同一个图像,但是有效地裁剪掉它的一部分,只显示我们想要的部分。使用这种技术,我们可以像这样修改我们的 CSS:

`a {
        background: transparent url(img/button.gif) no-repeat 0 0;
        height:40px;
        width:120px;
        display:block;
}

a:hover {
        background-position: 0 40px;
}`

现在只有一个 HTTP 请求,我们的按钮对鼠标悬停在它上面有即时反应。人们很容易对这项技术产生热情,并认为“嘿!让我们把整个网站上的每一张图片都放入一个巨大的 CSS 精灵中!”但是在你兴奋之前,关闭 Photoshop,看看为什么这是个坏主意:

  • CSS 精灵并不适合所有的事情。对于大小不可预测的元素(可能会随着浏览器中的font-size变化而调整大小或缩放),结果也可能是不可预测的。
  • 对于设计者和 CSS 开发人员来说,一个充满图像的大文件很快变得很难管理。
  • 决定图像中间的精灵需要改变尺寸会对图像的其余部分产生巨大的连锁效应。

相反,我们建议您在图像可能有多种状态的任何地方使用 CSS 精灵,例如前面演示的按钮。尽量避免允许使用精灵的元素以显示其他精灵或以不希望的方式裁剪它们的方式改变尺寸。如果这不可能,请确保在彼此之间添加足够的空间,这样,如果可视区域发生变化(例如当您增加字体大小或放大页面时),相邻的图像就不会显示出来。像往常一样,找到对用户和开发人员都有利的平衡点,并在组织内部决定如何实现这一点。一个例外是移动网站,那里的延迟可能很大,以至于每个额外的 HTTP 请求都会对页面呈现速度产生重大影响。

数据 URIs(统一资源指标)

除了将图像表示为一个单独的文件,还可以将图像编码成一大串数据,然后直接插入到文件中,如下所示:

<img src="data:image/png;base64,ABCDEFHIJKLMNOPQRSTUVWXYA" />

…或者:

background-image: url("data:image/png;base64,ABCDEFHIJKLMNOPQRSTUVWXYA");

这种方法有一个明显的好处:它为我们以这种方式包含的每个图像节省了额外的 HTTP 请求。在 CSS 精灵不合适的地方(例如,对于大小不可预测的元素),它们也能给你即时响应的悬停状态。不幸的是,它有更多的缺点:

  • 在比包含这些图像的文件更精细的级别上,对这些图像的缓存失去了控制
  • 该文件的表示实际上比常规图像文件大三分之一
  • IE 7 及以下版本根本不支持数据 URIs
  • IE 8 将数据 URIs 限制在 32 KB 或更少

由于这些限制,我们不建议使用数据 URIs,除非它们用于您可以准确预测或控制正在使用的浏览器的环境中。 19

缓存

缓存是存储经常访问的数据,以便可以更快地检索这些数据来提高性能的行为。缓存可能存在于几个不同的点:

  • 浏览器缓存—在客户端(浏览器)上,特定于机器的单个用户(尽管多个用户可以使用该帐户)
  • 【代理缓存(或共享缓存)—由互联网服务提供商(ISP)或第三方在客户端和服务器之间提供的缓存,由多个用户共享
  • 网关缓存—由 web 服务器(如缓存服务器或 cdn)的所有者实现的缓存,在许多用户之间共享

19Sveinbjorn Thordarson 在[www.sveinbjorn.org/dataurls_css](http://www.sveinbjorn.org/dataurls_css)提供了更多关于 URIs 数据的细节,以及生成它们的工具。

缓存主要服务于两个目的:减少延迟和减少网络流量。通常这两个目标符合每个人的最佳利益。每个人都希望用户尽快收到及时的信息和数据。每个人都希望传输尽可能少的数据,以进一步减少延迟并节省资金。作为一个高流量网站的所有者,我们唯一能够完全控制的缓存是我们已经实现的网关缓存。然而,我们可以给其他缓存一些命令和提示,并合理地期望它们会被遵循。这些命令是通过来自服务器的响应头来实现的,向浏览器和中间的代理缓存指示某个特定项在被再次请求之前是否应该在本地存储(以及存储多长时间)。

浏览器/代理缓存选择如何解释该信息最初是它的选择,并且不能保证我们使用的头将被正确实现,或者完全被读取,尽管大多数浏览器/代理现在已经标准化并且在它们如何处理这一点上相当一致。此外,浏览器中的用户设置可能会覆盖这种行为,在客户端完全禁用缓存的情况并不少见。缓存可以在短期内更有力地实现,这样,出现在多个地方的同一个图像就不需要再向服务器发出请求,并且单击后退按钮会立即得到响应。 二十

当提供内容时,由 web 服务器提供适当的缓存控制头。历史上控制缓存的第一种方法是 Expires HTTP 头,它可以设置为一个日期,在此日期之后,浏览器会向服务器重新请求数据。这种方法已被否决,原因如下:提供数据的资源(如网关缓存和代理缓存)之间的时间可能不同步,并且有必要以一定的时间间隔自动更新该值。相反,HTTP 1.1 规范定义了一个名为 Cache-Control 的新头,它存储一系列单独的值,让不同的缓存层做出智能决策,决定是提供文件的本地副本还是获取新的副本。这个决定基于两个因素: 21

  • 新鲜度—一个文件可以在一段时间内被标记为新鲜度,之后就是陈旧度,必须向服务器重新请求。
  • 验证—可以发出请求,让缓存机制知道本地存储的副本是否是最新的,而不必请求整个文件。

让我们快速看一下缓存机制通常是如何决定是否获取文件的新副本的。

  • 如果文件头指定不缓存该文件,或者该文件是通过安全(HTTPS)连接提供的,则不会缓存该文件。
  • 如果项目被认为是新的,它将被缓存,直到在这种情况停止后发出请求。
  • 如果一个项目不被认为是新的,将尝试验证它。如果发现它仍然有效,将继续提供缓存的项目。如果不是,将向服务器请求新的副本。

出于安全考虑,通过 HTTPS(安全 HTTP 连接)提供的内容不应该(通常也不会)缓存在本地或代理服务器上。

试图通过 HTML 中的 meta 标签来控制浏览器缓存是可能的,但很少有浏览器支持它们,因为浏览器的缓存机制会在 HTML 被解析之前检查数据并做出缓存决定。我们不推荐这种技术。

Cache-Control 头由几个逗号分隔的值组成。以下是一些更重要的价值观,其含义应该很好理解:

  • max-age—定义(以秒为单位)自请求以来文件应被视为新文件的时间段。
  • 必须重新验证—在特殊情况下,HTTP 允许缓存为过时的文件缓存提供服务。这个头告诉缓存机制不要遵循这种行为。
  • 不缓存—指示缓存机制从不缓存该文件。
  • no-store—指示缓存机制从不保留该文件的副本;缓存可以从内存中存储和提供,但不能写入磁盘。
  • public—明确表示共享缓存可以缓存某个项目,即使存在安全连接。
  • private—明确表示一个项目是可缓存的,即使有安全连接— ,但只在客户端缓存中
  • 代理-重新验证—在特殊情况下,HTTP 允许缓存为过时的文件缓存提供服务。这个头明确告诉共享缓存不要遵循这种行为。
  • s-maxage—定义(以秒为单位)自请求以来文件应被视为新鲜的时间段(但仅适用于共享缓存)。

下面是一个标题示例,它告诉所有代理永远不要缓存某个项目:

Cache-Control: no-cache, must-revalidate

以下是一个标题示例,它告诉所有代理将某个项目缓存一年,而不管该项目是否通过安全连接进行访问:

Cache-Control: max-age=31536000, public

验证器是代理用来检测一个项目是否已经改变的。在我们的第一个例子中,缓存机制将在每次请求时检查新版本。在第二种情况下,要等一年后才会检查。验证是向服务器发出特殊请求的行为,包括验证信息,如果缓存的副本仍然有效,服务器将不返回文件。缓存机制将使用两个主要的验证器。

第一个验证器是自文件最后一次修改以来的时间,它在名为 Last-Modified 的响应头中作为日期返回。代理可以发出一个名为 If-Modified-Since 的特定类型的请求,并在该请求中包含适当的日期,而不是发出两个请求(一个检查有效性,另一个检查本地副本是否无效)。

第二个验证器是在 HTTP 1.1 中引入的,叫做 ETag。ETag 是基于文件的内容构建的,是文件的一种指纹。当基于过时的缓存向服务器发出请求时,代理将发送一个名为 If-None-Match 的请求头,并包含存储的 ETag。 22

在这两种情况下,如果内容没有改变,则返回 304 HTTP 状态代码,这意味着没有修改,并指示缓存机制使用文件的存储表示。如果已更改,将返回包括文件内容在内的正常完整响应,并替换缓存的副本。

现代的 web 服务器会自动返回 Last-Modified 和 ETag 头,应该不需要进一步的配置。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**etag 的生成对于生成它们的文件来说是唯一的,对于提供它们的服务器来说也是唯一的。网关缓存或负载平衡服务器可能会使该报头不可预测。因此,它们通常不适合高流量的网站,应该使用 Last-Modified 标签。

使用浏览器时,有一些方法可以强制完全刷新并绕过缓存。例如,在 Firefox 中,按住 Windows 中的 shift 和 F5,或 OS X 中的 shift-command-R。这将发送一个名为 pragma 的额外请求头,其值为 no-cache,告诉服务器和任何共享缓存忽略请求中的任何缓存命令或缓存本地存储的数据,并从服务器返回最新版本。

我们应该缓存什么?

CSS 文件通常是非常静态的,因此非常容易缓存。这意味着它们的内容不会因其他因素而改变,如 cookies 或用户提供的内容。一个人收到的 CSS 文件内容与另一个人收到的内容完全相同。 23

在这种情况下,缓存是我们的朋友,可以显著提高各个级别的性能。因此,我们应该将图像、CSS 文件和其他静态文件的缓存设置为非常长期的。而且(通常)这些类型的文件没有安全考虑。这意味着不管这些文件是否通过安全连接被访问,我们仍然应该缓存它们,并向所有用户提供相同的副本。

在这种情况下,我们前面的长期缓存示例仍然适用:

Cache-Control: max-age=31536000, public

我们可以设置一个比一年更长的最大年龄,但是不太可能有人在一年后仍然在这个缓存中工作。

边缘缓存通常是一种有用且有效的技术,尤其是对于动态 CSS。我们将在下一章提到边缘缓存。


22 文件的指纹(或散列)(通常是 MD5 散列)使用算法来表示字符串。对照原始文件检查字符串可以让我们知道文件的内容是否已经更改。这通常用于确保您下载的文件是预期的文件并且没有损坏,但是在这种情况下,它也可以很好地检查文件是否已经更改。

动态创建的 CSS 文件不一定如此。在第九章中阅读更多相关内容。

应尽最大努力将所有 CSS 和 JavaScript 保存在外部文件中,而不是内嵌在页面中,因为 HTML 的缓存寿命较短,而 CSS 和 JavaScript 的缓存寿命较长。

版本控制

当处理最长时间的缓存文件时,版本控制成为一个真正的问题。当我们修改 CSS 文件时,缓存机制完全按照我们的要求运行,这意味着当我们想要停止缓存文件时,我们需要一个新的策略。唯一真正的解决办法是改变文件名。考虑下面的场景。

我们的文件 home.html 已经过修改,对标记进行了实质性的修改。我们的主样式表 style.css 也进行了修改,以适应这些变化。我们给了样式表一个长的缓存过期时间,以确保在不必要的时候不会被请求。突然间,我们所有的用户都在抱怨 home.html 看起来很破!发生的情况是,对 home.html 的更改已经到达我们的用户,但对 style.css 的更改还没有到达,因为该文件仍然在他们的本地缓存中可用。

解决这个问题的最好方法是版本化我们的 CSS。对 CSS 文件进行版本控制比听起来要简单得多。我们通过简单地手动修改文件名来包含版本号或日期来做到这一点。

例如,style.css 变成了 style.1.0.css。数字 1 和 0 分别表示“主要”和“次要”修订。每当我们做一个小的修改,我们就增加次要编号。如果这是一个戏剧性的修改,我们增加主要数字,并将次要数字重置为 0。因此,在这个实例中,我们只对 CSS 做了一点小小的更改,文件名变成了 style.1.1.css。我们的新标记引用了这个文件。我们在 CSS 文件上有很长的缓存过期时间,但在 HTML 上没有。如果用户正在获取我们的标记的最新版本,他们将获得 style.1.1.css,它还没有缓存在浏览器中,因为它是一个新文件。如果用户有我们的标记的旧版本,他们从缓存中获得 style.1.0.css,或者如果由于某种原因缓存中的那部分被清除或损坏,则作为新的请求。我们可以很容易地将文件命名为 style-2010-12-31.css 或 style-strawberry . CSS;重要的是文件名是唯一的。我们也可以通过在 URL 中包含一个查询字符串来突破缓存,比如 style.css?v=1.1,这将强制刷新文件,但会阻止我们保留以前的版本,从而阻止我们的向后兼容性。

多个文件同时存在的好处是,它确保了我们的系统的完整性,无论哪个版本的 home.html 运行在我们网站访问者的浏览器上。如果他们由于某种原因拥有旧版本(可能是共享缓存有问题,或者某个版本仍然被缓存),则仍然会请求 CSS 的适当版本。如果他们有更新的,同样是真实的。

作为我们构建过程的一部分(见第九章),我们可以让它自动化。

离线存储呢?

HTML5 为我们提供了一种离线存储机制,这样我们就可以在不向服务器发出任何请求的情况下提供页面和 web 应用程序。这可以通过html标签中的manifest属性来实现:

`

…`

清单文件(应该用我们指定的 mimetype text/cache-manifest)返回)包含关于哪些文件应该被自动缓存的信息。只有当缓存清单文件根据我们的正常缓存规则(即通过 ETags 或 Last-Modified)发生变化时,才会更新这些文件。清单文件示例如下:

CACHE MANIFEST /css/style.css /css/img/background.png /js/main.js

因为这个文件实际上覆盖了所有现有的缓存机制,所以当文件改变时,这个文件也需要改变。缓存清单中以散列符号开头的任何一行都被视为注释,这是注册对文件的更改的最简单方法,而不必修改清单本身:

`CACHE MANIFEST

Updated 2010/04/01

/css/style.css
/css/img/background.png
/js/main.js`

任何时候该文件被更改,所有声明的文件将被重新下载,您现有的缓存策略将不会生效。如果只有一个文件发生了更改(这种情况很常见),这是一种非常低效的管理缓存和性能的方式。由于这一点以及在撰写本文时浏览器支持较差,我们不推荐将它作为缓存常规内容的策略,尽管就其最初的用途而言,它仍然是一种很好的技术。

缓存清单在各种场景中都很有用,并且具有比这里列出的更复杂的语法,尽管它们超出了本书的范围。

渲染和解析

理解浏览器如何呈现我们的页面对于理解如何从您的页面获得最佳性能是至关重要的。浏览器首先读取 HTML,然后从上到下请求 HTML 中的项目。如果浏览器发现链接在页面底部的 CSS 文件,许多浏览器将什么也不呈现,直到页面中的所有内容都被加载,然后是 CSS 文件。在head标签中定位所有的link标签将确保这些标签首先被加载,然后浏览器可以逐步呈现页面。

每当浏览器找到@import 规则时,它会导入引用的文件,并在该指令之后的任何内容之前包含内容。在一些浏览器中(特别是 IE ),这阻止了文件的其余部分被读取和任何其他 CSS 文件被下载。这意味着浏览器必须等待该文件,然后才能继续下载页面的其余资源。这只是避免@import 规则的众多原因之一。

因为 JavaScript 文件是以线性方式读取的,所以它们会阻止页面的其他部分加载,直到它们被执行。因此,将它们放在尽可能靠近页面底部的位置,可以确保它们不会阻止我们并行下载和阅读任何其他文件。

当浏览器解析我们的 CSS 时,它从右向左读取我们的选择器,这可能非常不直观。尽管 id 非常有效,但是如果我们在它们后面加上其他不太具体的选择器,比如标记名,这些选择器将首先被读取。一旦浏览器找不到一个匹配的查询,它就会忽略选择器的其余部分,转到下一个规则。最快和最有效的选择器是那些 id 在选择器右边的选择器,id 非常具体,这通常意味着选择器的其余部分是不必要的。这将表明最有效的 CSS 可能是完全基于 id 的,但是用 id 填充我们的标记将(可能)是不明智的,并且很难管理。一如既往,必须在可管理和高效的代码之间找到平衡。

假设一个 ID 或类还包括一个标记名,如下所示:

div#mainContent {…} img.heroImg {…}

浏览器首先查询文档中具有这些 id 或类的所有元素,然后尝试进一步查询结果集中的标记。这很少是必要的,应该尽可能避免。

通用选择器(*)是您可以使用的效率最低的选择器:

body * {…}

尽管选择器看起来应该是一个简单的查询,但是它首先返回所有的元素,然后检查规则的下一部分(在本例中,元素应该在body标记内),这是一个非常低效的定位节点的方法。你应该尽可能避免它。您可以单独使用通用选择器,如下所示:

* {…}

有些人注意到在这种情况下,性能影响降低了,但是没有办法进行纯粹的测试。这将是一个“量子测试”,即观察行为会影响结果。除非必要,否则我们建议避免使用通用选择器。

CSS3 选择器更复杂,使用更多的资源来查询。因此,如果没有必要,您应该避免使用它们。id 总是 CSS 定位元素的最快方式,其次是类名。

CSS 文件的加载本身会导致一些浏览器阻塞,特别是 Internet Explorer。正如我们之前提到的,在页面加载了 JavaScript 之后,您可能会考虑“延迟加载”打印样式表。

然而,实事求是地说,CSS 性能很少会成为网站性能的瓶颈。你应该总是努力遵循最佳实践并保持 CSS 的高性能,但如果是以易读性或文件大小为代价的话就不要这样,因为这些更重要。

通过 JavaScript 改变属性

经常需要在与页面交互时修改样式,无论是简单的菜单可见性切换还是更复杂的事情。像下面这样的行很常见:

$("#elementID").css({    "height": "40px",    "width": "40px", });

在本例中,我们找到了 ID 为“elementID”的元素,并将其高度和宽度分别设置为 40 个像素。因为无法同时设置各个属性,所以这与下面的代码相同:

$("#elementID").css("height", "40px"); $("#elementID").css("width", "40px");

需要注意的是,以这种方式设置属性会强制在浏览器中重新绘制页面,这在性能方面代价很高。一切的定位都要重新计算两遍。如果我们设置更多的属性,它会发生更多次。相反,我们应该将它添加到我们的 CSS 中:

#elementID.modified {    height: 40px;    width: 40px; }

然后将我们的 JavaScript 更改为:

$("#elementID").addClass("modified");

现在,浏览器添加了该类,并且能够同时修改这两个属性,并且只强制进行一次刷新。虽然看起来我们在多个地方维护这些信息,但实际上 JavaScript 现在负责控制与元素相关的类,所有表示代码都保存在 CSS 文件中,这是应该的。如果我们必须在 JavaScript 的多个地方进行这种更改,那么将它抽象成一个简单的类显然是一种好的做法。

动画

动画是我们的页面将会做的最耗费资源的事情之一,可以通过很多方式实现(包括通过 CSS)。虽然我们不会深入 CSS 转换和过渡的细节,或者如何在页面上制作动画元素,但是我们会指出一些对性能有影响的事情。浏览器如何呈现页面对于理解如何获得最佳性能至关重要。

当通过 JavaScript 制作动画时,我们希望代码尽可能高效。除非我们在动画开始和结束之间的每一步都有一个类,否则我们不能一次轻易改变多个属性。为了用 JavaScript 制作动画,我们以特定的间隔改变 CSS 属性一定的量,直到我们达到目标值。例如,如果我们想让一个框从左向右移动,我们可以将“left”属性从 10px 修改为 100px,每 40 毫秒增加 10 个像素。这里有一系列可以遵循的好规则:

  • 动画尽可能少的属性。
  • 每次迭代做尽可能少的工作。
  • 将间隔时间设置为产生良好结果的最高值。将其设置得太低会导致性能不佳;设置太高会导致动画不稳定。

虽然通过 CSS 制作动画在撰写本文时仅在 WebKit 中得到真正的支持,但以这种方式制作动画带来了巨大的优势——最大的优势是这些动画是硬件加速的。可以编写代码,让 feature 检测 WebKit 动画事件,如果它们可用,就使用它们,如果它们不可用,就退回到 JavaScript。当通过 CSS 制作动画时,如果制作颜色动画,请注意在动画过程中浏览器会将所有内容转换为 RGBA。如果首先以这种格式提供颜色,您会看到更好的性能。

硬件加速

使用 CSS 过渡和转换将在支持它们的浏览器中使用硬件加速。通过将元素移动到 3D 空间,您可以欺骗浏览器对元素(及其子元素)强制进行硬件加速,如下所示:

… {    -webkit-transform-style: preserve-3d; }

虽然在某些情况下,这确实会提高性能,即使是在 2D 平面上,但在其他情况下,这可能会导致问题。移动设备不具备笔记本电脑和台式机的处理能力,不能很好地应对这种技术。此外,3D 平面中的元素通过不同的管道处理到其他元素,您可能会看到不同的文本渲染或抗锯齿结果。如果你能控制你的用户环境,这可能是一件有用的事情,但这不是我们推荐给典型网站的技术。

总结

这一章的目的是教你许多影响页面性能的因素,并向你展示一些你可以应用到你自己的 CSS 中以获得显著性能提升的最佳实践。在许多情况下,一种尺寸并不适合所有人,测试将是必要的,以了解您需要在这些不同的技术之间找到的平衡。这一支出将会带来回报,既体现在网站访问者的体验方面(因此也体现在他们对你的网站和组织的好感方面),也体现在你的带宽成本方面。

下一章关注的是动态 CSS,将教你如何为不同的用户提供不同的 CSS,以及如何使用 LESS 和 Sass 这样的预处理程序来使编写 CSS 更快、更实用、更有趣。

九、动态 CSS

到目前为止,这本书一直认为 CSS 文件是静态资产。也就是说,在每个实例中,从服务器请求并传递给站点访问者的内容是完全相同的。尽管这几乎总是实现 CSS 的预期行为和最高效的方式,但是可以根据其他因素定制 CSS 的输出,并向不同的用户交付不同的 CSS。

您可能想这样做有几个原因,有些是为了开发人员的利益,有些是为了用户的利益。从开发人员的角度来看,使用变量 1 或者动态填充 CSS 是很有用的。对于任何重复的代码,例如颜色或 CSS 的特定块,存储一次并在一个地方维护它会使代码更严格、更严格,修改起来也更容易、更安全。无论出于什么原因,在 CMS 驱动 CSS 的情况下,连接到服务器端组件或数据库可能是填充 CSS 或构建/输出选择器所必需的。有些工具使用动态行为来改进语言,使开发更有成效。

从用户的角度来看,一些网站选择为用户提供定制呈现给他们的 CSS 的能力,比如多种配色方案或版式。这可能是出于纯粹的美学原因,或者是为了解决诸如视力不佳或色盲等无障碍问题。

在本章中,您将了解以下内容:

  • CSS 扩展和预处理程序
  • 评估第三方预处理器
  • 用服务器端技术服务 CSS
  • 连续累计
  • 缓存注意事项

CSS 扩展和预处理程序

有几个项目旨在构建 CSS,并提供一些许多开发人员认为缺失的功能。变量、注释样式和快捷方式是解决这些问题的常见方法。为了使用这些特性,开发人员通常创建一个具有不同文件后缀的文件,并以新的语法在该文件中工作;然后使用一个编译器来读取这个文件并输出一个正常的 CSS 文件。在这个领域有两个大的竞争者,LESS 和 Sass,尽管它们处理许多相同的问题,但它们之间有重要的差异。


关于在规范中包含 CSS 变量的讨论正在进行中,并且已经进行了一段时间。Tab Atkins Jr 最新(个人)稿可在[www.xanthir.com/blog/b4AD0](http://www.xanthir.com/blog/b4AD0)阅读。

少了

LESS 是 Alexis Sellie 创建的预处理器。它使用类似 CSS 的语法,并对 CSS 进行了许多改进。我们在版本 1.0.41 上执行了我们的测试。它是内建的 JavaScript,所以你可以包含一个 LESS 文件,就像它是常规的 CSS 一样(尽管有不同的rel属性),和 less.js(来自http://lesscss.org),它将在页面加载时被编译。

`

`

然而,使用 less.js 会引入对 CSS 的 JavaScript 依赖,所以我们建议使用命令行编译器。LESS 的最新版本可用于 node.js(用于创建网络应用程序的服务器端 JavaScript 框架)。从[nodejs.org](http://nodejs.org)获取 node.js。一旦你安装了它,获得更少的包的最简单的方法就是使用节点包管理器。关于安装的说明可在[howtonode.org/introduction-to-npm](http://howtonode.org/introduction-to-npm)获得。最后,您可以运行这个命令来安装 LESS 包:

npm install less

结果如图 9-1 所示。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

***图 9-1。*安装少节点包

辛苦到此结束。??它安装好了。这个安装为您提供了一个主要工具:lessc—LESS 编译器。使用这个很简单。您只需运行这个文件,将您希望它编译的 LESS 文件的路径传递给它,LESS 将创建一个与源文件同名的 css 文件,但带有“CSS”后缀。默认情况下,lessc 将输出到 stdout——直接输出到终端(见图 9-2 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-2。 lessc 输出到 stdout

要输出到文件,请将 lessc 指向该文件。

lessc style.less > compiled/style.css

您也可以通过传递–x 作为参数来使用内置缩小(参见图 9-3 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-3。 lessc 缩小版


LESS 的最初版本是用 Ruby 构建的,安装起来简单得多——尤其是在已经包含 Ruby 的 OS X 上。其实 LESS 在本章初稿写完之后就正式转到 node.js 了!这表明 LESS 仍在积极开发中。

这种缩小没有 YUI 压缩器有效——它在开始时留下了尾部分号——所以我们建议配合使用另一种压缩器。

命令行工具是开始的一种方式,但是我们马上会看到其他更友好的方式。现在,让我们详细看看 LESS 提供给我们的额外功能。

变量

变量经常被认为是 CSS 最大的缺失特性之一,它可能非常强大和有用。您可以使用变量来存储您希望多次使用的任何信息,并在一个地方定义它。这意味着改变这个定义一次将影响我们使用该变量的每个实例。这是一种非常高效和稳健的工作方式。LESS 中的任何变量都以@符号为前缀。在 LESS 中声明变量非常简单,只需声明名称、冒号、值和行尾的分号。由于变量永远不会到达最终解析的 CSS,关于可以使用哪些字符的规则要简单得多;您正在为一个解析器而不是许多浏览器构建。

变量不能反复设置(它们不是变量! 3 )。事实上,LESS 中的变量是常量。一种常见的编程约定是,常量全部大写,下划线作为单词分隔符,这使得在 LESS 文件中直观地定位变量变得非常容易,但是您应该使用您和您的团队认为最合适的命名约定(许多人更喜欢使用类似 CSS 的格式)。下面是变量用法的一个基本例子:

`@PRIMARY_COLOR: #faa344;

#content {color: @PRIMARY_COLOR;}`

编译后,它会发出以下内容:

#content {   color: #faa344; }

如您所见,LESS 对 CSS 的外观有自己的想法:多行缩进两个空格,规则之间没有垂直间隙。因为这个想法是您将主要在 LESS 中开发,所以输出的格式远没有那么重要。

你可以多次重用你的变量。LESS 也理解作用域的概念,这意味着你可以在不同的上下文中声明同一个变量,并且只为那个上下文改变值。顶层(任何选择器之外)的任何变量都属于全局范围。选择器中声明的任何变量都将该选择器作为其作用域。一个变量只存在于它的作用域内,并覆盖任何更大作用域的变量。当引用一个变量时,LESS 将在引用的局部范围内查找,然后使用它遇到的第一个值沿着链向上找到全局范围。用一个例子可能更容易理解。

这少了:

`@PRIMARY_COLOR: #faa344;

#content {
   @PRIMARY_COLOR: red;
   color: @PRIMARY_COLOR;
}

#footer {
   color: @PRIMARY_COLOR;
}`

…编译成以下内容:


3 和可扩展样式表语言(XSL)中一模一样,在 XSL 中变量不是变量,因此命名非常非常糟糕。

#content {   color: red; } #footer {   color: #faa344; }

@PRIMARY_COLOR的第一个声明在任何选择器之外;它是全球性的。第二个声明是在#content的上下文中。此时,它会覆盖第一个声明,因为该声明的范围更广(不太具体)。第二个声明只存在于它的上下文中,所以一旦我们关闭了#content的大括号,这个变量就被丢弃了,取而代之的是我们的初始声明。当同一个变量在同一个作用域中被多次声明时,除了第一次声明之外的所有声明都被忽略。

通过用空格分隔,可以在一个属性中输出多个变量,如下所示:

`@VERTICAL_MARGIN: 10px;
@HORIZONTAL_MARGIN: 15px;

#content {
   margin: @VERTICAL_MARGIN @HORIZONTAL_MARGIN;
}`

它输出以下内容:

#content {   margin: 10px 15px; }

但是目前还没有将字符串连接在一起的方法——不留空格会减少为您添加的空格。为了证明:

`@VERTICAL_MARGIN: 10px;
@HORIZONTAL_MARGIN: 15px;

#content {
   margin: @VERTICAL_MARGIN@HORIZONTAL_MARGIN;
}`

在这段代码中,我们删除了输出变量之间的空格,但结果是一样的:

#content {   margin: 10px 15px; }

除了颜色,LESS 中的变量实际上可以表示我们在 CSS 中用作值或在值内使用的任何东西,从文本字符串到数字。

混合蛋白

一个 mixin 是一段很容易在另一个地方重用的代码。您可以将它视为类似于 JavaScript 中的函数或方法,或者从另一个 CSS 规则继承属性的一种方式。使用 mixins,可以在另一个 CSS 规则中使用一个 CSS 规则。通过在另一个规则中只使用前一个规则中的选择器,引用规则中的所有属性都将被包括在内。

这少了:

.callToAction {    color: #123; } #content {    .callToAction; }

…产生这个 CSS:

.callToAction {   color: #123; } #content {   color: #123; }

值得注意的是,原来的规则仍然存在于我们输出的 CSS 中。如果您只是希望这是一个不针对任何元素的可重用代码块,那么这不会像您希望的那样高效,并且会在输出中产生无关的代码。

您也可以以类似于函数或方法的方式使用 mixins。这意味着您可以将值传递到 mixin 中,也可以在没有传递任何值时使用默认值。少管这些叫参数混合。为了表明 mixin 接受参数,可以在它后面加上括号,括号内是变量的名称。如果有多个变量,则应该用逗号分隔,每个变量后面应该跟一个冒号和它们的默认值(如果适用)。例如,这少了:

`.box (@HEIGHT: 40px, @WIDTH: 40px) {
   height: @HEIGHT;
   width: @WIDTH;
}

#content {
   .box(20px,80px);
}
.callToAction {
   .box;
}`

…编译成这个 CSS:

#content {   height: 20px;   width: 80px; } .callToAction {   height: 40px;   width: 40px; }

正如您所看到的,这个方法有一个额外的好处,就是不再在我们的代码中输出 mixin!如果我们意识到这一点,我们可以使用这个技巧来使不需要参数的 mixins 不再出现在我们的输出中,只需在它们的名称后面加上空括号,如下所示:

`.callToAction () {
   color: #abc;
}

#content {
   .callToAction;
}`

这产生了这个 CSS:

#content {   color: #abc; }

这种方法不适用于比单个类名或 ID 更复杂的选择器,所以不能用标签名或派生选择器定义 mixin 这将导致错误。LESS 足够聪明,知道任何不包含属性(或只包含变量)的选择器都不会在编译后的 CSS 中输出。 4

实际上,不指定默认值会使这些参数成为“必需的”如果你引用一个没有设置默认值的 mixin,并且你没有传递值给它,这将导致一个错误。

Mixins 对于抽象出规范中尚未最终确定的实验属性特别有用。对于不同的浏览器,它们可能会有略微不同和复杂的语法,并使用供应商前缀来分隔它们。将这些属性保存在单独的模块中,可以使它们在规范和支持发生变化时易于修改,并将这些变化反映在整个代码中,同时最大限度地减少人为错误,并消除每次使用时写出每个特定于供应商的版本的需要。


4LESS 的早期版本允许您包含文档中存在的任何选择器,而不管选择器由多少个元素组成。

嵌套规则

可以将规则嵌套在另一个规则中,然后沿着选择器链向上构建 CSS 的最终选择器。再举一个例子:

#content {    .callToAction {       a {          color: #321;       }    } }

结果是:

#content .callToAction a {   color: #321; }

这既是一种祝福,也是一种诅咒。通过缩进和嵌套,很容易模仿 HTML 的结构,这是一种非常直观的工作方式。然而,产生的选择器可能冗长而具体,这不太可能是我们想要的 CSS 结果。这里有一个更复杂的例子:

#content {    .callToAction {       a {          color: #111;       }       p {          color: #111;       }       div {          color: #111;       }    } }

结果是:

#content .callToAction a {   color: #111; } #content .callToAction p {   color: #111; } #content .callToAction div {   color: #111; }

这个 CSS 不仅比它可能需要的更具体,而且还可以通过多个逗号分隔的选择器更有效地完成,如下所示:

#content .callToAction a, #content .callToAction p, #content .callToAction div {   color: #111; }

这种方法还鼓励您编写特定于某些元素的 CSS,而不是可重用的类,这是一种低效的 CSS 编写方式。除非您使用这种方法对混合或变量进行分组(稍后将在“名称空间”和“访问器”小节中介绍),或者您使用类名或 ID 作为名称空间,否则我们不建议以这种方式使用嵌套规则。

可以混合嵌套的规则和属性,如下所示:

#content {    position: absolute;    p {       color: red;       a {          color: blue;       }    } }

结果如下:

#content {   position: absolute; } #content p {   color: red; } #content p a {   color: blue; }

您还可以嵌套伪类,这是该特性的一个很好的用途。通过简单地包含冒号和伪类,您可以一次生成几行。未编译的 LESS:

a {    :hover {       color:blue;    }    :visited {       color:red;    }    :link {       color:green;    }    :active {       color:yellow;      } }

…以及由此产生的 CSS:

a :hover {   color: blue; } a :visited {   color: red; } a :link {   color: green; } a :active {   color: yellow; }

您还可以使用& combinator 来输出嵌套规则和属性中的整个选择器。这对于链接类名特别有用:

#content {    .callToAction {       color: red;       &.disabled {          color: gray;       }    } }

结果是:

#content .callToAction {   color: red; } #content .callToAction.disabled {   color: gray; }

然而,如果你试图使用& combinator 将父选择器放在当前选择器之后,LESS 将重新排列这些选择器:

#content {    body & {       color:blue;    } }

它变成如下:

#content body {   color: blue; }

这几乎肯定不是您的意图(您会期望选择器是body #content)。如果您理解嵌套选择器的潜在负面影响,它们会非常有用。

操作

LESS 允许我们对值执行基本的数学运算,并在输出 CSS 之前对其求值。可用的运算有加、减、除和乘(没有模运算符)。这里有一个简单的例子:

`@BORDER_TOP: 1 + 1;
@BORDER_RIGHT: 1 - 1;
@BORDER_BOTTOM: 2 * 2;
@BORDER_LEFT: 6 / 2;

.callToAction {
   border-top:@BORDER_TOP;
   border-right:@BORDER_RIGHT;
   border-bottom:@BORDER_BOTTOM;
   border-left:@BORDER_LEFT;
}`

结果如下:

.callToAction {   border-top: 2;   border-right: 0;   border-bottom: 4;   border-left: 3; }

运算符(+或-)前后必须有一个空格,这一点很重要(其他空格字符会导致错误),否则在对变量执行运算时,结果可能有些不可预测。LESS 也可以将这些运算符应用于单位。等式中只需要出现一个单位,更少的会假设所有其他单位都是这个单位。尝试使用多个单位执行计算将会导致错误,并且无法编译。 5 这里有个例子:

`@BORDER_TOP: 1px + 1px;
@BORDER_RIGHT: 1 - 1px;
@BORDER_BOTTOM: 2em * 2;
@BORDER_LEFT: 6 * 20%;

.callToAction {
   border-top:@BORDER_TOP;
   border-right:@BORDER_RIGHT;
   border-bottom:@BORDER_BOTTOM;
   border-left:@BORDER_LEFT;
}`


5 这不同于 CSS3 中提议的calc()函数(由 Firefox 的最新测试版支持——使用–moz-cal()供应商前缀——并计划由 IE9 支持),它足够聪明地混合了单元,因为它是在页面呈现时计算的,而不是预先计算的。更多信息请点击这里:[www.w3.org/TR/css3-values/#calc](http://www.w3.org/TR/css3-values/#calc)

结果如下:

.callToAction {   border-top: 2px;   border-right: 0px;   border-bottom: 4em;   border-left: 120%; }

请注意,%被视为一个单位,并不充当模数运算符 6 ,也不用于计算其他值的百分比。另请注意,LESS 在使用其内置压缩时,即使值为零(在不必要的情况下),也会继续附加该单元,但附加的缩小脚本可以消除这种情况。

您不能对简单的字符串值执行操作,但是 LESS 足够聪明,可以对颜色(包括 HSL 和 HSLA 颜色)执行类似的计算,尽管它会将颜色转换为十六进制。实际上,当在计算中使用 RGBA 和 HSLA 颜色时,当前版本甚至将它们转换为十六进制,完全失去了 alpha 通道,所以我们建议不要这样做。作者承诺将在下一个版本中解决这个问题。对命名的颜色执行计算是不可能的,因为 LESS 会将其视为一个字符串,而不是一种颜色—事实上,使用加法运算符时,LESS 会在第一个颜色后附加一个空格,这很可能不是您想要的结果。在计算中使用颜色时,LESS 会将颜色分解为各个分量,根据每个分量计算颜色,然后重新附着它们。您甚至可以在计算中使用多种颜色,LESS 将独立评估红色、绿色和蓝色通道(或色调、饱和度和亮度)。也可以在操作中使用变量。这里再举一个例子:

`@BORDER_TOP_COLOR: #aabbcc / 2;
@BORDER_RIGHT_COLOR: @BORDER_TOP_COLOR + #111;
@BORDER_BOTTOM_COLOR: rgb(13,26,39) * 2;
@BORDER_LEFT_COLOR: rgba(10,20,15,0.1) + @BORDER_RIGHT_COLOR;

.callToAction {
   border-top-color:@BORDER_TOP_COLOR;
   border-right-color:@BORDER_RIGHT_COLOR;
   border-bottom-color:@BORDER_BOTTOM_COLOR;
   border-left-color:@BORDER_LEFT_COLOR;
}`

编译后的代码如下:

.callToAction {   border-top-color: #555e66;   border-right-color: #666f77;   border-bottom-color: #1a344e;   border-left-color: #708386; }


6 模运算符用于求除法运算后的余数。

如你所见,@BORDER_LEFT_COLOR的整个 alpha 通道已经丢失。在使用颜色操作时,您需要意识到这个障碍。

您可以使用这些方法来使颜色变亮或变暗,并使用单个主要变量来创建该颜色的阴影。这是一个非常强大的方法,只基于几个初始颜色来生成配色方案。

颜色功能

LESS 中新引入了一些用于处理颜色的内置函数:

  • lighten(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 lighten()允许您按百分比将颜色变浅。

  • darken(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 darken()允许您将颜色加深一个百分比。

  • saturate(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 saturate()允许你进一步按百分比饱和一种颜色。

  • desaturate(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 desaturate()允许您按百分比进一步降低颜色的饱和度。

  • spin(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 spin()允许您修改颜色的色调度数(正或负)。

  • fadein(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 fadein()允许您按百分比增加颜色的不透明度。省略%符号仍会导致参数被视为百分比,因此 0.1 与 0.1%相同。更少不会增加超过 100%的不透明度。

  • fadeout(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 fadeout()允许您按百分比降低颜色的不透明度。省略%符号仍会导致参数被视为百分比,因此 0.1 与 0.1%相同。“更小”不会将不透明度降低到 0 以上。

对于其中的每一个,LESS 会在应用变换之前将颜色转换到 HSL 颜色空间。然后,它会将颜色转换回十六进制结果,除非不透明度小于 100%,在这种情况下,它会将其转换为 RGBA。下面是这些颜色函数的使用示例:

`@COLOR: #aabbcc;
@COLOR1: lighten(@COLOR, 10%);
@COLOR2: darken(@COLOR, 10%);
@COLOR3: saturate(@COLOR, 10%);
@COLOR4: desaturate(@COLOR, 10%);
@COLOR5: spin(@COLOR, 10);
@COLOR6: spin(@COLOR, -10);
@COLOR7: fadeout(@COLOR, 50%);
@COLOR8: fadein(@COLOR7, 25%);

a {
   color: @COLOR;
   :link {
      color: @COLOR1;
   }
   :visited {
      color: @COLOR2;
   }
   :hover {
      color: @COLOR3;
   }
   :active {
      color: @COLOR4;
   }
   .callToAction {
      :link {
         color: @COLOR5;
      }
      :visited {
         color: @COLOR6;
      }
      :hover {
         color: @COLOR7;
      }
      :active {
         color: @COLOR8;
      }
   }
}`

结果是:

a {   color: #aabbcc; } a :link {   color: #cad5df; } a :visited {   color: #8aa2b9; } a :hover {   color: #a3bbd3; }   a :active {   color: #b1bbc5; } a.callToAction:link {   color: #aab5cc; } a.callToAction:visited {   color: #aac1cc; } a.callToAction:hover {   color: rgba(170, 187, 204, 0.5); } a.callToAction:active {   color: rgba(170, 187, 204, 0.75); }

这是一个很好的演示,说明了如何用一种颜色来生成一个完整的配色方案。还有提取单个 HSL 通道的内置函数,称为(可预测的)hue()saturation()lightness()。您可以使用这些函数基于其他颜色的通道来构建颜色。这些功能中的任何一个都可以与其他功能相结合。这里有一个例子:

`@COLOR1: #123;
@COLOR2: #456;
@COLOR3: #789;
@NEW_COLOR: lighten(hsl(hue(@COLOR1),saturation(@COLOR2),lightness(@COLOR3)), 10%);

a {
   color: @NEW_COLOR;
}`

使用我们的新颜色,结果如下:

a {   color: #8ea1b4; }

名称空间

在 LESS 中,可以在嵌套的 mixins(名称空间)中将属性组合在一起,并通过使用>字符将这些属性作为组进行引用。这对于封装和保持代码整洁非常有用。和往常一样,这是最容易用例子来证明的:

#box () {    .square {       width:80px;       height:80px;    } } .content {    #box > .square; }

…结果是:

.content {   width: 80px;   height: 80px; }

这种技术非常有用,尽管它在 LESS 的早期版本中有些缺陷,在早期版本中,您可以访问名称空间中的单个属性和变量。可以嵌套任意多层次的名称空间。

正在评论

LESS 还支持 C 风格、单行注释以及块注释,但是只有 CSS 风格的注释会在编译后的 CSS 中输出,除非您使用内置的缩小功能。这里有一个简单的例子:

/* A regular CSS block level comment: */ #content {    color: black; //A single line C-style comment }

结果是:

/* A regular CSS block level comment: */ #content {   color: black; }

导入

LESS 支持@import 指令直接将文件包含在其他文件中。您可以以这种方式包含 CSS 文件或更少的文件。当包含较少的文件时,文件后缀是不必要的。对于本例,我们需要演示几个文件的内容:

  • 风格. less:

`@import “style2”;

#content {
   color: red;
}`

  • style2.less:

`footer {
   color: red;
}

@import “style3.css”;`

  • style3.css:

header {    color: red; }

编译 style.less 现在会产生以下结果:

@import "style3.css"; footer {   color: red; } #content {   color: red; }

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注意:**注意到什么了吗?LESS 足够聪明地意识到,当我们对 CSS 文件而不是 LESS 文件使用@import 时,我们并不打算让它组合这些文件,而让我们的代码保持原样。但更好的是,LESS 知道@import 命令需要在文件的顶部,并代表我们移动了它。LESS 的早期版本甚至聪明地意识到,当多个选择器具有相同的内容并且彼此直接相邻时,它应该用逗号分隔这些选择器并将它们分组在一起——遗憾的是,这种行为似乎已经消失了。

这种方法对于保持特征分离非常有用。在任何 LESS 文件中声明的任何变量或混合对其他人都是可用的,包括那个文件。这使得创建一个变量的配置文件变得很容易,例如,一个变量的配置文件用来驱动剩下的较少的代码。重要的是要记住,构建配置文件会产生一个空的 CSS 文件,因为其中没有标准的 CSS 代码。您必须始终编译包含其他文件的最底层文件,以构建正确的 CSS。这意味着编译该文件,即使它没有任何变化。

结论

使用附带的编译器只是一种方式,你可以创建 CSS 文件,从他们不太对口。如果使用 node.js,可以在 node.js 应用程序中直接使用节点包。 7 对于那些更喜欢图形用户界面(GUI)而不是命令行的人来说,一个名为 Less.app 的应用程序在[incident57.com/less/](http://incident57.com/less/)可供 OSX 使用,它具有监视文件夹或文件,并自动编译的能力(参见图 9-4 )。


7[lesscss.org/#-client-side-usage](http://lesscss.org/#-client-side-usage)阅读更多关于在 node.js 中使用 LESS 的信息。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-4。 LESS.app 版本 2.3.1

还有流行的 ide、文本编辑器和 web 框架的插件,比如 Apache、Grails、PHP ( [leafo.net/lessphp/](http://leafo.net/lessphp/))和。NET ( [www.dotlesscss.org/](http://www.dotlesscss.org/))。使用这些插件可以减少在读取时(当用户试图访问文件时)为您编译的代码,因此您不需要手动编译,缓存可以避免性能问题。

不幸的是,少也有不好的一面。虽然简单易学,但很少有开发人员不太熟悉。你需要严格实现它,并教育你的开发人员:任何开发人员不小心直接修改 CSS 代码,都会在下次编译时丢失更改。例如,嵌套规则可能会使控制选择器的特异性变得困难。最糟糕的是,当试图在 Firebug 或任何其他 web 检查器中调试 CSS 时,您正在检查的文件(输出的文件)不会以任何直观的方式与您的源文件相关联(行号不匹配,选择器可能不相同,等等),除非您使用的是 LessPHP。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **提示:**如果你正在使用 LessPHP,一个名为 Fireless ( [addons.mozilla.org/af/firefox/addon/fireless-for-firebug/](https://addons.mozilla.org/af/firefox/addon/fireless-for-firebug/))的 Firebug 插件将使你能够调试你的 less 代码,只要你启用 LessPHP 的“debug_info”选项。

此外,代码和行为在不同版本之间发生了巨大的变化,没有变更日志或文档来解释发生了什么变化,或者如何修改现有代码来应对这些变化。如果您使用的是 LESS.app、LessPHP 或。此外,不同版本之间的接口和代码可能会有很大的不同,因为每个代码版本都需要更新端口,可能需要一段时间才能跟上。请阅读本章后面的“评估第三方预处理程序”,了解如何将这些评论应用于您在开发过程中使用的任何第三方代码。

也就是说,LESS 是编写 CSS 的一种有吸引力和有趣的方式。然而,我们提到的问题使我们很难推荐它用于生产系统;如果功能和实现变化如此之大,而没有记录的理由,并且错误悄悄进入系统,这表明没有回归测试发生——这不是你想在高流量网站上进行 beta 测试的那种事情。在官网http://lesscss.org/了解更多关于 LESS 的内容。

萨斯

Sass 代表语法上令人敬畏的样式表,就像首字母缩写词一样,它肯定是有前途的。Sass 比 LESS 存在的时间更长,已经是第三次修订了。因为 Sass 太成熟了,所以我们先介绍得少一点似乎有点不公平,尤其是现在它们的语法如此相似。然而,由于 LESS 与 CSS 和 mixins 的相似性,它的吸收速度更快。也就是说,Sass 的开发者们很快就开始效仿,而且做得优雅而恭敬。

Sass 的原始语法以 Haml 风格缩进。 8 在这个语法中,空格,比如缩进和回车,很重要,暗示着血统和结构。尽管 Python 开发人员喜欢这一点(Python 也将空白视为其语法的一部分),但许多其他开发人员习惯于随意使用空白来格式化他们的文档。我们将巧妙地回避这一讨论,而是说从 SASS 3 开始,语法已经被称为 Sassy CSS (SCSS)的东西所取代。

SCSS 与 LESS 的相似之处在于,它的结构与 CSS 完全一样,并且每个有效的 CSS 文件也将是有效的 SCSS 文件。从视觉上看,旧语法(.sass文件)和新语法(.scss文件)的区别在于,新语法有大括号,而旧语法只有缩进;新的以分号结束规则,而旧的用回车。从功能上来说,使用 Sass 与使用 LESS 非常相似,但是 Sass 的文档要全面得多,而且有许多函数是 LESS 中没有的。

Sass 和 LESS 的原始版本一样,是用 Ruby 构建的,所以要安装它,首先需要安装 Ruby。如果使用的是 Windows,可以从[rubyinstaller.org/downloads/](http://rubyinstaller.org/downloads/)获取安装程序;如果您有 OS X,它已经安装。然后,要安装 Haml(包括 Sass),在命令行输入(见图 9-5 ):

gem install haml


Haml 是一种简洁的 HTML 模板语言。在[haml-lang.com/](http://haml-lang.com/)阅读更多关于 Haml 的信息。从 3.1 版本开始,Sass 将与 Haml 分开提供。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-5。 安装 Haml(包含 Sass)

你完蛋了。您可能需要将 Ruby 添加到您的 PATH 变量中——如果这是真的,那么当您运行上面的命令时,就会得到警告。将文件编译成 CSS 的 Sass 命令叫做sass。同样,您可以在单个文件上使用它:

sass style.scss style.css

如果您未能提供输出文件作为参数,结果将简单地输出到标准输出(见图 9-6 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-6。 Sass 输出到 stdout

你可以观察文件的变化来自动生成编译后的 CSS,语法略有不同:

sass --watch style.scss:style.css

或者,您可以观察整个文件夹,并将该文件夹中的任何更改输出到另一个文件夹,如下所示:

sass --watch path/sass:path/css

在这种情况下,新文件以与原始文件相同的名称命名,但后缀更改为. css。

总的来说,Sass 提供了更多的功能。LESS 的文档简单而缺乏,而 Sass 的文档却很全面且写得很好。为此,我们建议您参考他们在[sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html](http://sass-lang.com/docs/yardoc/file.SASS_REFERENCE.html)的文档,并将重点关注许多主要特性和差异,因为否则只会重复他们的内容。我们将关注 SCSS 语法,而不是不太像 CSS 的老方法。

变量

变量在 Sass 中使用稍微不同的语法。最初,创建变量使用以下语法:

!variable = value

由于转向了更像 CSS 的语法,这种方法已被弃用。新样式类似于 LESS,但使用美元符号作为前缀:

$variable: value;

Sass 还使您能够在输出中直接包含变量值(称为“插值”),这允许您连接变量,甚至在选择器中使用它们。这是通过用大括号将变量名括起来并在前面加上一个散列符号来实现的。下面的 SCSS:

`$id: product;
$property: color;
$value: green;

#content .#{KaTeX parse error: Expected 'EOF', got '}' at position 3: id}̲ {    #{property}:#{$value};
}`

…编译成以下内容:

#content .product {   color: green; }

这使得变量在 Sass 中比在 LESS 中更加通用。默认情况下,Sass 也总是将输出格式化为多行,缩进两个空格,最后一个右括号在最后一行,而不是在新的一行。与 LESS 不同,Sass 理解变量的布尔值(真或假)。这使你能够使用条件逻辑,你可以在本章的后面读到。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 提示: Sass 实际上支持四种不同的输出格式:压缩、压缩、扩展和嵌套。如果您正在使用命令行,您可以通过--style开关选择使用哪一个,如下所示:

sass test.scss test.css --style compressed

Sass 压缩非常好,实现 this 和@import 指令足以模拟连接和缩小的构建脚本。

在 Sass 中,变量实际上是变量而不是常数(这就是为什么在我们的例子中我们用 camel case 而不是大写来命名它们)。您可以根据需要多次更改变量值:

`$color: red;
$color: green;
$color: blue;

#content {
   color: $color;
}`

…编译成:

#content {   color: blue; }

在 Sass 中,变量的作用域也有所不同。由于无法区分创建和修改变量,一旦创建了变量,任何在比初始范围更具体的范围内创建同名变量的尝试都将修改变量,而不是创建另一个变量。然而,作用域仍然受到重视,因为在某个作用域创建的变量在不太具体的作用域中不可用。这更容易用一个例子来解释:

`$color1: red;

#content {
    c o l o r 1 : g r e e n ;     color1: green;     color1:green;   color2: blue;
   color: $color1;
   background-color: $color2;
}

.callToAction {
   color: c o l o r 1 ;     / / T h e f o l l o w i n g l i n e i s c o m m e n t e d o u t , s i n c e i t w o u l d c a u s e a c o m p i l a t i o n e r r o r .     / / color1;    //The following line is commented out, since it would cause a compilation error.    // color1;   //Thefollowinglineiscommentedout,sinceitwouldcauseacompilationerror.   //color2 has not been created in the scope of this block.
   //background: $color2;
}`

…结果是:

`#content {
  color: green;
  background-color: blue; }

.callToAction {
  color: green; }`

在这种情况下,在.callToAction中引用$color2会导致编译错误,因为$color2还没有在该范围内创建(见图 9-7 )。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

图 9-7。 Sass 报告了范围问题的明显错误

注意,如果命令行编译器遇到错误,Sass 将创建一个空文件,而不是完全忽略该命令。

嵌套选择器

Sass 中的嵌套选择器的工作方式与 LESS 中的完全一样,只有一个显著的区别:当将当前选择器的放在之后时,Sass 正确地使用了&组合符。这个 SCSS:

#content {    a {       .callToAction & {          color: red;       }       &.externalLink {            color: blue;       }    } }

…编译成以下内容:

.callToAction #content a {   color: red; } #content a.externalLink {   color: blue; }

在这种情况下,Sass 的行为与我们预期的一样,而 LESS 会输出#content a .callToAction

LESS 和 Sass 都不会输出没有属性的规则。但是,它们都很乐意在同一个选择器中输出相同的属性和值对:

#content {    color: red;    color: red; }

…编译成:

#content {   color: red;   color: red; }

正如您所看到的,它重新格式化了代码,但是并没有减少不必要的代码。这同样适用于代码的压缩版本,包括 Sass 和 LESS。

条件逻辑

LESS 非常缺少条件逻辑和循环行为(将在下一节描述),Sass 提供了这些。支持 If 和 else 子句。您可以使用大括号来包含要计算的 SCSS;和@if、@else 和@elseif 作为条件命令。一个非常简单的例子如下:

`$christmas: true;

@if $christmas {
   #content {
      color: red;
   }
} @else {
   #content {
      color: black;
   }
}`

产生的 CSS 如下:

#content {   color: red; }

您还可以在选择器中使用 if 语句,就在单个属性周围。支持标准运算符:

| `==` | 等于 | | `!=` | 不等于 | | `<` | 不到 | | `>` | 大于 | | `<=` | 小于或等于 | | `>=` | 大于或等于 | | `and/or/not` | 用于组合条件的多个部分 |

这里有一个简单的例子:

`$lines: 10;

#content {   
   @if $lines < 10  {
      height: 20px;
   } @elseif $lines == 10 {
      height: 40px;
   } @elseif $lines > 10 {
      height: 60px;
   }
}`

结果如下:

#content {   height: 40px; }

虽然这可能会使 SCSS 变得更加复杂和难以阅读,但它也使变量和 Sass 作为一个整体变得更加强大。

循环

Sass 支持两种循环方法:@for 和@while。对于从一个数字到另一个数字的简单迭代,@for 是最合适的。语法很简单,包括声明一个变量和定义循环的起点和终点。变量可以在每次迭代中正常使用。这里有一个例子:

@for $counter from 1 through 3 {    #product#{$counter} {          color: green;    } }

生成的 CSS 如下所示:

`#product1 {
  color: green; }

#product2 {
  color: green; }

#product3 {
  color: green; }`

Sass 还支持“从 n n”而不是“从 n n”,这仅仅意味着变量必须小于而不是等于最后一位数字。在我们的例子中,这会给我们两次迭代,而不是三次。

@while 循环适用于更复杂的迭代,例如,对于循环的每次迭代,您可能希望增加不止一个步骤。循环将继续,直到条件评估为假。同样,语法很简单:

$ counter:1;

@while $counter <= 6 {    #product#{$counter} {       color: green;    }    $counter: $counter + 2; }

结果如下:

`#product1 {
  color: green; }

#product3 {
  color: green; }

#product5 {
  color: green; }`

这些类型的控制指令是对 Sass 的一个非常受欢迎的补充(也是 LESS 中明显缺少的)。

正在评论

Sass 支持单行的、C 风格的注释,在这方面的行为与 LESS 完全一样。

访问者

Sass 使您能够使用@extend 关键字将选择器附加到前面的规则中——如果选择器只有一个元素的话。尽管要追加的选择器可以具有任何复杂性,但是它所应用到的规则不能有任何嵌套的选择器。下面的代码演示了这一点:

`.box {
   width:80px;
   height:80px;
}

div#content.main {
   color: red;
}

#content #product {
   color: black;
}

.redBox {
   @extend .box;
   color: red;
}
#content div a.callToAction {
   @extend div#content.main;
   text-decoration: underline;
}
.productBox {
   //The following line would cause an error
   //@extend #content #product;
}`

它编译成以下内容:

`.box, .redBox {
  width: 80px;
  height: 80px; }

div#content.main, #content div a.callToAction {
  color: red; }

#content #product {
  color: black; }

.redBox {
  color: red; }

#content div a.callToAction {
  text-decoration: underline; }`

您可以在一个声明中扩展多个规则。如果我们取消了标记行的注释,错误将如下所示:

Syntax error: Can't extend #content #product: can't extend nested selectors         on line 22 of style.scss   Use --trace for backtrace.

虽然复杂,这种技术可以是强大的。但是,如果处理特殊性问题,您必须小心,因为 Sass 在扩展时会重新排列选择器的顺序,并且很容易将选择器放在文件中比您预期的更早的位置。

混合蛋白

在 Sass 中,Mixins 遵循稍微不同的格式。在 LESS 中,几乎任何简单的选择器都可以用作基本的 mixin,而在 Sass 中,选择器是用@mixin 关键字和一个惟一的名称创建的,并且用@include 关键字引用,从而使 mixin 和规则非常独立。在某些方面,这可能被认为不太灵活,但它也鼓励模块化代码,使代码具有更清晰的意图,并避免您可能会面临的一些问题(例如,当不必要时,mixin 会在最终的 CSS 中输出,或者试图使用复杂的选择器重用 CSS 块作为 mixin)。这里有一个例子:

`@mixin border {
   border: solid 1px black;
}

#content {
   @include border;
}`

输出如下:

#content {   border: solid 1px black; }

在 Sass 中也可以将参数传递给 mixin,就像在 LESS 中一样,但是省略一个必需的参数会导致明显的错误。这里有一个真实的例子:

`@mixin borderRadius($radius) {
   -moz-border-radius: $radius;
   -webkit-border-radius: $radius;
   border-radius: $radius;   
}

#content {
   @include borderRadius(5px);
}
#product {
   @include borderRadius(15px);
}

.callToAction {
   //The following line would cause an error
   //@include borderRadius;
}`

它编译成以下 CSS:

`#content {
  -moz-border-radius: 5px;
  -webkit-border-radius: 5px;
  border-radius: 5px; }

#product {
  -moz-border-radius: 15px;
  -webkit-border-radius: 15px;
  border-radius: 15px; }`

如果我们取消了标记行的注释,由于没有为$radius参数设置默认值,将会发生以下错误(在 stdout 中):

Syntax error: Mixin borderRadius is missing parameter $radius.         on line 15 of style.scss, in borderRadius’
        from line 15 of style.scss
  Use --trace for backtrace.`

颜色

和 LESS 一样,Sass 支持颜色的数学运算。Sass 理解所有颜色格式,包括命名的颜色(LESS 不理解),并能正确地与 alpha 通道一起工作。Sass 还公开了许多特定于颜色的函数,这在 LESS 中是没有的。首先,有个颜色访问器,允许您公开颜色变量的特定属性:

  • red(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 red()返回颜色的红色通道,为 0 到 255 之间的整数。

  • green(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 green()返回颜色的绿色通道,为 0 到 255 之间的整数。

  • blue(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 blue()以 0 到 255 之间的整数返回颜色的蓝色通道。

  • hue(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 hue()返回一种颜色的色相,度数在 0 到 359 之间。 9

  • saturation(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 saturation()以百分比形式返回颜色的饱和度。

  • lightness(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 lightness()以百分比形式返回颜色的亮度。

  • alpha(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 alpha()返回 0 到 1 之间颜色的 alpha 通道。 10

虽然hue()saturation()lightness()都有更少的颜色,但其他颜色访问器是 Sass 特有的。

使用这些组成完整颜色的独立部分,您可以基于另一种颜色的特定属性来构建颜色,这对于基于几个原色构建配色方案来说是一种非常高级的技术。

我们将展示几个使用这些属性的快速示例。首先,我们将剖析一个命名的颜色,并使用组成部分来构建一个 RGBA 颜色。这是 SCSS:

`$color: red;

r e d : r e d ( red: red( red:red(color);
g r e e n : g r e e n ( green: green( green:green(color);
b l u e : b l u e ( blue: blue( blue:blue(color);
a l p h a : a l p h a ( alpha: alpha( alpha:alpha(color);

#content {
   color: rgba($red, $green, $blue, $alpha);
}`

下面是编译后的 CSS:

#content {   color: red; }

有趣的是,Sass 选择将其作为命名颜色输出,这实际上是最有效的方法,也是最不容易解释的方法。它对所有可以被解析为命名颜色的颜色都这样做,这在读取输出的 CSS 时有时会更加混乱,但是您将在 SCSS 开发,所以除了在调试时,这应该不会出现问题。

事实上,Sass 总是将颜色转换成它认为最有效的输出。在可能的情况下,它会将它们输出为已命名的颜色。否则,它将使用十六进制颜色。最后,如果有一个阿尔法通道,它将输出 RGBA。这是另一个例子:

`$color: rgb(1, 2, 3);
  #content {
   color: $color;
}

$color: rgba(1, 2, 3, 0.5);

#product {
   color: $color;
}

$color: hsla(90deg, 50%, 50%, 0.5);

.callToAction {
   color: $color;
}`


9 计算一种颜色的色调非常复杂,需要运用一些相当高级的数学知识。你可以在[en.wikipedia.org/wiki/Hue](http://en.wikipedia.org/wiki/Hue)了解更多关于色相的内容。

10 为了向后兼容以前版本的 Sass,功能opacity()alpha()完全相同。

以下是输出结果:

`#content {
  color: #010203; }

#product {
  color: rgba(1, 2, 3, 0.5); }

.callToAction {
  color: rgba(128, 191, 64, 0.5); }`

对于开发人员来说,这是一个有趣的决定,知道这一点很重要。这是一个值得称赞的方法,因为它通常能产生最有效的输出。

Sass 还为您提供了颜色变异函数:修改颜色变量的值的函数。我们将列出其中的每一项,并对其进行简要描述:

  • lighten(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 lighten()允许您按百分比将颜色变浅。

  • darken(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 darken()允许您将颜色加深一个百分比。

  • adjust-hue(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 adjust-hue()允许你逐渐调整颜色的色调。这个和spin()里的少一样。

  • saturate(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 saturate()允许你进一步按百分比饱和一种颜色。

  • desaturate(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 desaturate()允许您按百分比进一步降低颜色的饱和度。

  • grayscale(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 grayscale()将把一种颜色转换成它的单色等价物。

  • complement(color)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 complement()将一种颜色转换成它的互补对应物。

  • mix(color, color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 mix()将两种颜色结合在一起。如果提供了 amount 属性(以百分比的形式),它将使用第二种颜色的那部分。如果省略,Sass 会将两种颜色平均混合。

  • opacify(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 opacify()将按特定量(0 到 1 之间)增加颜色的不透明度(或降低透明度/alpha)。它将不会(明显地)增加超过 1 或低于 0 的值。 11

  • transparentize(color, amount)

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 transparentize()opacify()相反,将按照所提供的量(0 到 1 之间)增加颜色的透明度/alpha。像opacify()一样,萨斯绝不会让一个阿尔法通道超过 1 或者小于 0。 12

虽然lighten()darken()adjust-hue()saturate()desaturate()opacify()transparentize()的等效物都有,但都比较少;其他颜色变异是 Sass 独有的。

使用这些函数(以及我们之前提到的颜色访问器),可以实现非常复杂的颜色处理和计算。您可以生成一个完整的配色方案或变亮/变暗颜色,以便创建渐变,等等。一如既往,这里有一个简单的例子:

`$color: blue;

#content {
   background-color: c o l o r ;     c o l o r : c o m p l e m e n t ( color;    color: complement( color;   color:complement(color);
}`

结果如下:

#content {   background-color: blue;   color: yellow; }


11 为了向后兼容以前版本的 Sass,功能fade-in()opacify()完全相同。

12 为了向后兼容以前版本的 Sass,功能fade-out()transparentize()完全相同。

导入

Sass 支持@import,和 LESS 完全一样,只是有两个小的区别。Sass 不会将@import 命令移动到文件的顶部,这虽然不是一个明显的遗漏,但却是一个遗憾,因为它会导致无效的 CSS,其中@import 规则将不会被应用。当然,如果您打算直接将 CSS 作为输出的一部分,您只需要将文件后缀重命名为。scss(或。文件越少越好)。如果您使用如下命令:

@import "style.css";

Sass 会将其转换为以下内容:

@import url(style.css);

少了就不会这样了。这是一个不重要的改变,因为根据 CSS 2.1 规范,这两种语法都是可以接受的。但是,生成的 CSS 要大三个字符。因为@import 命令在 CSS 中并不常见(我们建议不要使用它们),所以我们觉得这没什么好担心的。

结论

Sass 还有许多其他特性,如果您打算将它作为开发过程的一部分,那么您应该通读大量编写良好的文档,并精通它们。

LESS than Sass 的应用越来越广泛——也许是因为它更简单,从一开始就看起来像 CSS 所以找到员工、应用程序支持和与它配合良好的插件可能会更容易。不可否认,Sass 更强大,但是,如果您选择使用这些预处理程序中的一个,您应该权衡每一个的利弊。

虽然不如 less 多产,但是存在用 Apache 在读取时编译 Sass 的方法,以及用于大多数主要可扩展 ide 和 Ruby gems 的插件。无论您的服务器设置如何,只要您愿意,您都可以将 Sass 集成到其中。

你可以在http://sass-lang.com/阅读更多关于 Sass 的内容。

尽管许多 CSS 开发人员对这两种技术(LESS 或 Sass)中的任何一种都有抵触情绪,因为他们认为这会带来太大的负担,但这确实是一种没有根据的批评。因为它们都支持 CSS 样式的语法,所以将原始 CSS 文件重命名为。少还是。scss 并添加您认为合适的新功能。真正的批评应该是对构建过程的修改、调试中的困难、对第三方的依赖以及所需的额外技能。然而,在您的组织中,您可能觉得这些问题是可以接受的,因为这些技术给您带来了许多好处。

评估第三方技术

在使用任何第三方代码作为开发过程的一部分之前,应该进行彻底的调查,以确保这些代码是适当的和安全的。尽管人们很容易对库、框架或预处理程序的能力感到兴奋,但是一旦它们被实现,以后要改变它们就很昂贵和困难了。因此,首先对它们进行彻底的研究,并确保团队同意使用它们是至关重要的。

需要考虑的一些事项如下:

  • Are many developers actively working on the code?

    如果只有一两个开发者,这应该是一个紧急的危险信号。很少的开发人员处理一段代码意味着很少的输入来源,这反过来意味着对用例或潜在问题的更狭隘的看法。它还表示风险,即开发人员可能会失去兴趣或由于某种原因不再能够对代码做出贡献。CSS 的世界变化(相对)很快,技术需要更新以适应这种变化。

  • Is the code mature (old)?

    没有被广泛使用的代码不太可能被很好地测试。尽管较新的技术往往是最令人兴奋的(我们当然鼓励尝试代码以获得想法或灵感),但我们不建议在生产系统或高容量网站中使用它们,在这些地方边缘案例可能会很多。

  • Is the technology in widespread commercial use?

    同样,在更多地方使用的代码可能更安全,也更容易测试。流行技术的结果是社区、第三方文档(如书籍和博客/网络文章)和行业热情(因此可以雇佣更多的开发人员)。

  • Is the source code available?

    作为对代码停滞不前且不再被开发的未来场景的最后一招,可用的源代码将允许您的公司在内部进行未来的修改。这剥夺了允许第三方代表您有效地测试和开发代码的好处,但是保护您免受一定程度的风险。

  • Is there comprehensive documentation?

    好的文档显然有助于技术的实现和使用,同时也展示了认真的开发,这是一个好的迹象。

  • Is there a test suite?

    测试套件的存在应该表明测试良好的代码,并减轻对未来版本中回归错误的担忧。

  • Are there change logs?

    当新版本的代码发布时,很可能会对旧的实现产生影响。清晰的更改日志准确地指出了哪些内容发生了更改,以及哪些内容可能会导致问题或影响您现有的代码库。旧的方法和语法应该被弃用,至少在几个版本中不能被删除,以便让技术的消费者有机会将他们的代码更新到新的接口。

如果您对正在评估的技术的大多数问题的回答都不是肯定的,那么您应该认真思考实现这项技术是否会成为未来技术债务的来源,以及您试图解决的问题是否无法用其他方法更好地解决。

使用服务器端技术提供 CSS 服务

正如我们之前提到的,目前所有的浏览器都不关心你的 CSS 文件有什么后缀。 13 只要你用正确的 mime 类型(text/css)提供包含你的 CSS 的文件,浏览器就会很高兴地阅读它,然后继续它的日常事务。这意味着您可以使用您选择的服务器端技术,为您提供与预处理器相同的功能,甚至更多。您可以使用 PHP 连接到数据库,从那里填充变量,并将它们直接插入到代码中;或者编写自己的 ASP.NET 颜色处理函数。只要引用一个包含 CSS 的文件并提供正确的 mime 类型,这就可以了。

一个示例实现是具有许多子品牌的大型网站。尽管 CSS 基本上是相同的,但是服务器可以生成多个版本的 CSS 文件,这些文件具有从数据库中填充的不同配色方案。该数据库可以通过 CMS 进行填充,从而实现网站与网站之间一致的自动化布局,以及可以由出版部门而不是开发人员制作的配色方案。

您也可以使用这种技术在不同的情况下提供不同的 CSS。例如,您可以检测用户工作的部门(如财务、人力资源、IT 等),并根据查询字符串或数据库条目向他们提供不同的 CSS。您还可以为用户提供他们独有的 CSS 尽管这种技术很难在服务器端缓存,并且会影响性能和带宽。更好的做法是为用户提供 CSS 选项或配色方案,并为他们提供有限数量的带有共享 URL 的 CSS 文件(或 querystring 配置)之一,以便可以有效地缓存结果。

与 LESS 和 Sass 相同的缺点也适用于此,或许程度更大。当试图调试你的 CSS 时,行号将与你的源代码不一致,选择器可能在你的源文件和结果文件之间看起来完全不同。也就是说,使用变量或数据库来维护代码的能力是允许非技术人员(如发布或内容团队)修改网站美学的一种很好的方式,自定义代码将使您对输出有更多的控制。当然,另一个不利之处是,很难雇佣廉价或低端的开发人员,因为学习曲线更陡峭,他们不能立即变得富有成效。

和往常一样,是否实现这种风格的 CSS 生成取决于您的特定场景。

持续集成(CI)

正如我们之前提到的,使用构建脚本来操作 CSS 是可能的,通常是出于性能的原因。虽然您可以手动运行这些脚本,但是能够以自动方式运行它们以部署到其他环境是很有用的。通常,一个组织应该有四个主要环境(可能每个都有几个):

  • Development

    虽然 CSS 作者通常在他们自己的机器上编写和测试 CSS,但当他们需要将他们的代码与其他人所做的更改集成,以更有效地测试他们作为项目的一部分所构建的内容时,他们会部署到开发环境中。这种环境通常是不可靠的,因为变化被非常频繁地部署到其中,因此对于团队进行高级测试是有用的,但对于彻底的或探索性的测试则不是。 14

  • Integration or QA

    此环境的部署频率较低,通常以预定的时间间隔进行。由于代码不经常更改,这是进行更彻底测试的好环境。通常,同时修改您的项目所依赖的代码的其他团队会使用这个环境来测试您的代码之间的连接。

  • Staging

    外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传尽管开发和集成环境很少是实际生产环境的精确副本,但试运行环境的行为和配置应尽可能接近生产环境。对此环境的任何部署都应该是可靠的、可靠的、经过测试的代码;在这一点上,应该执行用户验收测试(UAT)。这种环境通常也是发布或内容团队在将它们推向生产环境之前用来测试它们对站点的修改的环境。

  • Production

    这是为你的用户提供页面的实时网站。


其他文件类型也是如此,比如图像、JavaScript 或字体。

当使用版本控制时,通常只有当您确信您的变更将按预期工作时,才将其推送到主存储库。一旦您提交了它们,您可能想要做的第一件事就是将它们部署到开发环境中。

虽然可以简单地连接到适当的服务器,从源代码管理存储库中获取最新版本的文件,然后手动运行任何构建脚本,但是这非常耗时,因此不是一种有效的工作方式。

持续集成(CI)系统解决了这个缺点。可以将服务器设置为自动检测存储库中的变更——或者在预定义的时间间隔后通过轮询,或者通过版本控制系统中的钩子。每当检测到更改时,服务器获取文件的新版本,然后构建项目。成功构建项目后,它会自动部署到开发环境中。

这种方法有几个优点:

  • 任何测试都可以在项目构建之后、部署之前运行。这意味着,如果您有可以在客户端运行的测试,您可以构建一个临时环境,在服务器上自动运行测试——可能在许多操作系统和浏览器中——如果有问题或测试失败,选择不部署到开发环境。这被称为破坏构建。(你可以在第十章中读到更多关于测试的内容。)
  • 任何构建脚本都可以自动运行。
  • 所有用户都会收到关于任何构建问题的警告。如果有人破坏了构建,某种通知系统可以让所有其他开发人员知道,并且可以就谁应该修复它以及应该如何修复它进行对话。在问题解决之前,其他开发人员不应该提交存储库。
  • 同一系统可用于手动(或自动)部署到其他环境。

14 “探索性测试”描述了无脚本测试,测试人员依靠他们的知识在站点上“捣乱”,寻找意想不到的结果或错误。

通常使用交通灯系统,其中一个小应用程序驻留在用户的菜单栏或系统托盘中。如果构建成功,它会亮绿色;如果构建失败,它会亮红色。

如果您已经选择合并构建脚本来缩小或连接您的代码,CI 可以在它被设置为部署时为您运行这些脚本。然而,您应该尝试包含启用某种调试模式的能力(如下一章所述)——至少在开发环境中——以便当测试失败或发现问题时,它们易于解决。

您可以运行的另一个测试是查看站点的结果文件是否有效,或者是否有任何生成警告。在这种情况下,您可能仍然会选择部署,但是应该通知签入导致警告的文件的开发人员。

CI 鼓励开发人员养成各种好习惯。如果它与自动化测试相结合,它可以使代码更加健壮,并节省许多浪费的时间——以及更早地提醒开发人员和测试人员注意问题。

巧妙构建脚本

前端工程师杰德·托马斯(Jade Thomas)提出了自己的方法,一次性解决了许多问题,而不仅仅是缩小和连接预定义的文件列表。选择在她的 CSS 中使用特定于浏览器的供应商扩展作为 WebKit 的目标,构建脚本解析这些属性,并将其复制到同一属性的所有其他特定于供应商的版本中,从而提供一种抽象的方法来处理规范中的变化,并在出现变化时添加更多特定于供应商的扩展。接下来,为 IE 的版本设置一系列自定义的供应商前缀,如–ie6--ie7-等(不是–ms-,因为它已经存在),构建脚本可以解析并输出这些前缀,作为该浏览器的合适工具。

但是现在,聪明的部分。构建脚本实际上为 IE 的每个版本输出一个文件,为所有其他浏览器输出一个文件,而不是输出 hacks。然后,这些文件可以通过条件注释提供给适当的浏览器,这样每个浏览器都只能获得针对它的代码。LESS 或 Sass 这样的预处理器可以模拟一些这种行为,但远没有那么好。这种方法的好处是显著的:

  • 原始文件不需要编译就可以工作(在 WebKit 上)——这意味着当我们调试时,我们仍然可以看到有问题的 CSS 在哪一行。
  • 当验证工具支持厂商前缀时,我们的 CSS 实际上会进行验证! 十五
  • 输出中永远不会有黑客攻击。
  • 构建脚本的修改版本可以在不修改行号的情况下输出 IE 版本,这也使调试变得容易。
  • 每个浏览器只发出一个 HTTP 请求。
  • 每个属性的特定于浏览器的代码并存,使得维护这些文件变得容易。

最新版本的 W3C CSS 验证器([jigsaw.w3.org/css-validator/](http://jigsaw.w3.org/css-validator/))允许你将厂商前缀显示为警告,而不是错误。

遗憾的是,这种技术还没有公开的版本或文档,但是如果您的过程证明了这一点,我们鼓励您考虑模仿这种行为。

缓存注意事项

当提供任何类型的动态文件时,缓存都会成为一个问题。如果您提供的文件是经过预处理的,但在其他方面是静态的,并且提供给许多用户,那么您只需要配置您的 web 服务器,将这些文件视为可缓存的(并且您仍然可以使用版本控制—如前一章所述—在必要时有效地绕过缓存)。如果这些文件是在对用户做出反应的任何时候处理的,而不是在此之前,那么这些文件也可以通过不同的机制进行缓存。但是,对于您的用户来说,最好是在请求之前主动准备好类似这样的文件。这意味着没有一个人会承受性能影响的冲击,服务器会承受可预测的(而不是不一致的)压力。

如果文件是动态修改的(可能是基于每个用户),并且存在太多的潜在变化,预先创建这些文件是不合理的,那么从服务器端进行缓存就变得不切实际了(尽管浏览器仍然可以缓存内容),因此性能下降是不可避免的。为了解决这个问题,您可以尝试限制潜在的变化,缓存获得最多流量的变化,或者确保您的 web 服务器没有额外负载的问题。在这种情况下,边缘端缓存可以帮助最小化负担。边缘缓存目前只能在(X)HTML 标记中使用,但是我们在这里提到它是为了您的兴趣,因为它可能与您选择的缓存策略有关。

边缘端缓存使用一种简单的标记语言边缘端包含(ESI) 来动态请求响应的特定部分,并将其余部分作为静态内容。这需要由某种 CDN 或缓存服务器来实现和解析,以便用户点击它并返回正确的响应。一些组织已经实现了它,对于高性能的网站来说,这是一种非常有效的技术。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传 **注:**参见[en.wikipedia.org/wiki/Edge_Side_Includes](http://en.wikipedia.org/wiki/Edge_Side_Includes)支持 ESI 的厂商列表。

ESI 语言规范在 2001 年 8 月提交给了 W3C,但没有得到回应。然而,ESI 的反应和吸收是积极的,如果你想使用它,它是足够成熟和简单的,呈现最小的风险。一段非常简单的 ESI 标记如下所示:

<esi:include src="http://yourcompany.com/uncachable.content"/>

Varnish16支持 ESI,Akamai 等很多 cdn 和缓存服务器也是如此。你可以在[www.w3.org/TR/esi-lang](http://www.w3.org/TR/esi-lang)阅读更多关于 ESI 的信息。

总结

这一章解释了一些你可以拥有动态的和自我构建的 CSS 文件或响应的方法。这些技术并不总是合适的,并且在雇用新员工时会导致生产力下降,但是在正确的情况下可以使开发变得更加简单和愉快。如果它们适合你的网站,并且你已经考虑了这些方法的缺点,你就不应该担心实现它们。

下一章关注的是调试和测试,将教你需要知道什么来找到这种平衡,确保你充分利用你的资源,以及定位你的代码的问题并修复它们。


16 Varnish 是一款出色的软件,它极大地提高了许多 web 服务器的缓存能力。在[www.varnish-cache.org/](http://www.varnish-cache.org/)阅读更多信息。

内容概要:本文围绕基于FFT算法的MATLAB傅里叶级数3D可视化展开研究,结合Matlab代码实现信号处理中的频域分析与维图形展示,旨在通过快速傅里叶变换(FFT)将时域信号转换为频域特征,并利用维可视化技术直观呈现周期信号的频谱结构。文中可能涵盖傅里叶级数的数学原理、FFT算法的实现流程、Matlab编程细节以及3D绘图的技术方法,帮助读者深入理解信号频域特性及其可视化表达。此外,文档还列举了大量相关的科研仿真项目,如故障诊断、路径规划、优化算法等,体现出该研究在工程与科研领域的广泛应用背景。; 适合人群:具备一定Matlab编程基础和信号处理知识的校学生基于FFT算法的MTALAB傅里叶级数3D可视化研究(Matlab代码实现)、科研人员及工程技术人员,尤其适用于从事信号分析、故障诊断或可视化研究的相关从业者; 使用场景及目标:①掌握FFT在Matlab中的实现方式及其在信号频谱分析中的应用;②学习如何将傅里叶级数结果进行3D可视化以增强数据分析的直观性;③为后续开展机械故障诊断、电力系统分析、通信信号处理等领域的研究提供技术参考与代码基础; 阅读建议:建议读者结合文中提供的Matlab代码进行实际操作,逐步调试并理解每一步的信号变换与图形绘制逻辑,同时可参考附带的网盘资源获取完整代码示例和其他相关仿真模型,提升实践能力与科研效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值