CSS3-之书第二版-全-
CSS3 之书第二版(全)
原文:
zh.annas-archive.org/md5/1742445c1217caaa066cc06ca2c55203译者:飞龙
前言
让我简单介绍一下我认为你是谁:你是一位已经手工编写 HTML 和 CSS 几年的网页开发人员;你已经很熟悉创建复杂的布局,并且不仅能区分 div 和 span,还知道如何区分 bold 和 strong;你对 CSS3 有些了解,可能已经开始尝试一些更具装饰性的功能,比如圆角,但你希望深入理解其基础知识。
The Book of CSS3 帮助你利用你对 CSS2.1 的优秀知识,使学习 CSS3 变得更容易。我不会解释 CSS 的基础知识(除非偶尔提醒一下),因为我假设你已经了解它们。我也不会逐步演示如何使用 CSS 制作列表导航或图像库,因为我假设你能将本书中的示例应用到你自己想要构建的任何项目中。
我将向你介绍现在可以用 CSS3 做的事情,并展示未来你能够用它做什么。同时,我会将 CSS3 规范中密集的技术语言翻译成简单实用的语言。
简而言之,我希望为你的工具箱增添一些新工具,让你能够制作出更酷的东西。
本书的范围
CSS 可以应用于多种类型的媒体;事实上,几乎任何能够显示 HTML 或 XML 的设备,也能显示 CSS 规则,尽管有时是有限制的形式。CSS3 还专门有模块处理分页媒体,如 PDF 或打印材料,并且支持盲文、手持移动设备(即手机而非智能手机)、电传打字机和电视。可能性如此广泛,我无法一一覆盖。
本书聚焦于计算机屏幕上的 CSS。所有演示都是为最常见的桌面浏览器编写(并经过测试),并针对桌面和笔记本电脑用户进行了优化。本书中涵盖的几乎所有新 CSS 特性,无论是为智能手机、平板电脑还是其他设备开发,它们都应当能够正常工作,但我不能保证在这里展示的示例会完全如图所示。
逐章介绍
这是本书将要涉及的内容的简要概述:
第一章 介绍了 CSS3,讲解了它的历史,并审视了 W3C 的标准化过程。它还描述了我在示范代码中使用的语法。
第二章 介绍了媒体查询,这项技术对于自适应和响应式网页设计方法至关重要。
第三章 和 第四章 介绍了新的选择器:第三章介绍了属性选择器,第四章介绍了伪类选择器。
第五章 展示了如何选择自定义网页字体,以及一些让你更好地控制字体渲染的属性。
第六章 延续了排版主题,介绍了一种新属性,用于为文本添加阴影,并控制文本块的显示方式。
第七章 也是关于文本的,这一章解释了如何让文本跨多个列流动。
第八章 和 第九章 介绍了背景和边框模块,包括对现有背景属性的扩展以及为元素边框添加装饰效果的全新方法。
第十章 解释了如何使用不透明度和透明度,并介绍了一些新的颜色模型。
第十一章 介绍了 CSS 渐变,一种在两种或多种颜色之间过渡的方法,用于制作独特的背景装饰。
第十二章 和 第十三章 展示了如何进行视觉变换,改变元素的二维和三维外观。
第十四章 介绍了动画,例如两个值之间的过渡变化和复杂的定时动画。
第十五章 介绍了 Flexbox,一种基于可用空间布局元素的新方法。
第十六章 也是关于页面布局,涵盖了新的值单位以及如何根据内容进行尺寸计算和调整元素的大小。
第十七章 是布局章节的最后一章,介绍了新的 CSS 网格布局模块。
第十八章 介绍了视觉效果,例如混合元素的背景层或将一个元素与另一个元素混合,使用图形过滤器,以及如何使用简单形状裁剪元素。
第十九章 通过展望 CSS 的(可能的)未来来结束本书:介绍一些目前相对实验性的、新的属性和功能,这些可能会在将来被浏览器完全实现。
附录和更多资源
本书的末尾有两个附录。第一个是本书中讨论的 CSS 特性在浏览器中的支持情况快速参考,第二个是在线资源、实用工具和有趣示范的列表。
此外,在本书的网站 www.thebookofcss3.com/ 上,你可以找到附录的最新版本以及书中使用的所有示例和演示。如果我有任何错误,你也可以在这里找到完整的勘误表。
除了附带的网站外,你还可以在我的博客《坏链接》上找到更多关于 CSS3(以及其他新兴的网页技术)方面的文章 (www.broken-links.com/)。欢迎在这些网站上留言或与我联系。
第二版导言
我在 2010 年开始着手编写本书的第一版。那时仅仅四年时间,但这段时间里,技术格局变化之大!2010 年,iPad 刚上市几个月,Android 还未爆发,而回顾自己网站的访客统计数据,来自所有移动设备的访问仅占会话的 11.6%——与本文撰写时的 54.8% 相比差距巨大。
自本书第一版出版以来,我们见证了 Safari 四个主要版本和 Internet Explorer 三个版本的发布。Firefox 已经成为自动更新的“常青”浏览器,Chrome 从 Web-Kit 引擎切换到了自己的 Blink 引擎,而 Opera 停止了对 Presto 引擎的开发,转而使用 Blink。
此外,像 Sass 和 LESS 这样的预处理器的兴起将编程语言的强大功能引入了我们的样式表,并且极大地改变了我们编写 CSS 的方式。如今,大多数专业开发者将预处理器作为网站创作工具集的核心组件。
自第一版发布以来,许多 CSS 规范也发生了变化。有些已经被废弃,更多的是新增的内容。2010 年的 CSS3 在跨浏览器实现上差异较大,但如今这些差异已经大大减少,因为浏览器厂商更加重视遵循标准。
换句话说,本书的第二版不仅仅是对第一版的简单修改;每一章都经过了全面修订,反映了规范的变化,并删除了过时的实现信息和不在规范中的实验性属性。某些章节(特别是关于媒体查询、Flexbox、网格布局以及 CSS 未来的章节)几乎是全新的,同时我还新增了关于值和尺寸、混合模式、滤镜效果以及遮罩的新章节。
让我们为接下来的四年变革干杯。
第一章:1
介绍 CSS3

在第一章中,我将介绍本书中使用的代码约定,并讨论一些 CSS3 独有的语法,但在进入这些内容之前,让我先讲讲 CSS3 的历史。显然,你不需要了解它的历史就能使用 CSS3,但我认为了解一些关于当前 CSS3 状况的背景信息是很重要的。
CSS3 是一个不断变化的规范。规范中的一些部分被认为是稳定的,并且在现代浏览器中得到了良好的实现;其他部分应该被视为实验性的,已部分实现但程度不一;还有一些部分仍然是理论提案,根本没有实现。一些浏览器创建了自己的 CSS 属性,这些属性在任何 CSS3 规范中都没有描述,或许永远不会被描述。
所有这些意味着,了解标准化过程是如何工作的,以及每个新属性的实现级别,对于理解如何在现在和未来的代码中使用 CSS3 是至关重要的。
什么是 CSS3 以及它是如何诞生的
首先,我想讨论一下什么是 CSS3——以及它不是——以及它的形式。W3C 对 CSS3 的处理方式与对 CSS2 的处理方式大相径庭,因此这个概述应该能帮助你理解如何以及何时使用 CSS3,并且为什么它在不同浏览器中的实现如此多样。
CSS3 的简要历史
CSS 的最后一个主要版本是 CSS2.1,这是对最初于 1997 年发布的 CSS2 规范的修订版。尽管自那时以来一直在进行开发和审查,但许多人会惊讶地发现,直到 2011 年,CSS2 才成为 W3C 的“正式”推荐标准。(稍后我会详细讲解推荐过程。)更令人惊讶的是,2009 年发布的 Internet Explorer 8(IE8)号称是第一个完整支持 CSS2.1 规范的浏览器。
在过去几年中,关于新修订版——CSS3 的讨论愈发增多。我说是“新”,但实际上,CSS3 的工作始于 1998 年,也就是 CSS2 发布的次年。然而,浏览器对 CSS2 的实现一直令人沮丧地不一致,以至于 W3C 决定暂停所有新版本的工作,转而改为开发 CSS2.1,标准化现实中 CSS 的实现方式。2005 年,所有 CSS3 模块被重新归为工作草案状态,编辑和审查过程重新开始。
多年来,Internet Explorer 主导了不断扩展的互联网用户市场,并且没有表现出任何想要实现 CSS3 的迹象。然而,在过去十年左右,出现了一系列新的浏览器来竞争用户,这种选择的多样性导致了功能上的军备竞赛。而 CSS3 正是这一竞赛的受益者之一。每个浏览器都希望为开发者和用户提供最新的网络技术,而随着 CSS3 规范大部分已经编写完成,实现甚至添加新功能就变得不言而喻。
所以今天我们已经来到了这里,CSS3 规范正在积极开发中,各种浏览器正在实现它,广大开发者社区正在使用它、研究它并撰写相关文章。这是一个健康的局面,也是几年前我们无法预见到的情况。
CSS3 是模块化的
为每个基于标记的文档创建默认的样式语言是一项巨大的工作,W3C 意识到这将需要许多年才能完成。W3C 成员意识到,在考虑和讨论一些更为深奥的特性时,他们不想耽误一些更加明显、需求较高的功能,因此决定将 CSS3 拆分为多个模块。每个模块可以由不同的作者以不同的速度进行工作,实施和推荐过程——我稍后会讨论——可以错开进行。
这就是为什么,不是有一个单一的、庞大的 CSS3 规范文档,而是有 CSS3 基础用户界面模块、选择器第 3 级、媒体查询等等。这些模块中有些是 CSS2.1 的修订版本,有些是新创建的,但都属于 CSS3 的范畴。
我觉得让人烦恼的少数事情之一(我是个随和的人)是,许多博客上会有人抱怨:“我想使用 CSS3,但它需要几年才能准备好。”这简直是胡说八道;一些 CSS3 模块在所有现代浏览器中已经有相当稳定的实现,更多的模块也只需要几个月就能投入使用。如果你想等到所有模块在每个浏览器中都 100%实现,你将永远在等待。
所以 CSS3 已经到来了,其中一些已经可以立即使用——你只需要小心使用它。
没有 CSS3
好吧,我意识到这句话有点挑衅性,但从技术上讲它是正确的。随着 CSS 变得模块化,每个模块都会被指定一个级别数字,以标明它已经经历了多少次修订。一些成熟的模块,比如选择器,已经达到了第 4 级;本书中涉及的许多模块,比如字体,达到了第 3 级;而一些非常新的模块,比如 Flexbox,只有第 1 级,或者可能正进入第 2 级。
这意味着 CSS 是一个活标准:正如我之前提到的,不再会有单一的版本发布;每个模块将以自己的节奏推进;并且随着新功能的范围确定,新模块将不断加入。CSS3 仅仅是一个便捷的简写,表示“自 CSS2.1 以来开发的 CSS 特性。”CSS4 永远不会存在。最终,版本编号将不再使用,我们将只有 CSS,并且不同模块会有不同的级别。
但是让我们不要气馁!我将在本书中继续使用上文所定义的 CSS3 这个术语,作为新 CSS 特性的便捷简写。这种标签有助于理解,并且意味着我不需要更改本书的标题!
模块状态和推荐过程
当我在本书中讨论各个模块时,我有时会提到该模块的状态。状态由 W3C 设定,表示模块在推荐过程中所处的进度;但请注意,状态并不一定表示某个模块在任何浏览器中的实现程度。
当一个提议文档首次被接受为 CSS3 的一部分时,它的状态会被指定为工作草案(Working Draft)。这个状态意味着文档已经发布,现在可以供社区审查——在此情况下,社区指的是浏览器厂商、工作组和其他相关方。文档可能会作为工作草案停留很长时间,经过多次修订,并不是所有文档都会通过这一阶段,且文档可能会多次返回此状态。
在文档从工作草案(Working Draft)进入下一个阶段之前,它的状态会变为最后通牒(Last Call),这意味着审查期即将结束,通常也表明文档准备好进入下一个阶段。
下一个阶段是候选推荐(Candidate Recommendation),这意味着 W3C 认为该文档是合理的,最新的审查没有发现重大问题,并且所有技术要求都已得到满足。此时,浏览器厂商可以开始在浏览器中实现文档中的属性,以收集真实世界的反馈。
当两个或更多浏览器以相同的方式实现了某些属性,且没有出现严重的技术问题时,文档可能会进展为提议推荐(Proposed Recommendation)状态。这个状态意味着该提案已经成熟并实现,准备接受 W3C 顾问委员会的批准。一旦获得批准,提案就会成为推荐(Recommendation)。
重申一下我之前简要提到的内容,推荐过程和实现过程并不总是以相同的方式进行。一个模块可以在所有浏览器中得到良好实现,但仍然保持工作草案状态——例如,我写这本书时,过渡(Transitions)模块(第十四章)正是这一状态。相反,一个模块可能处于候选推荐状态,但实现仅限于部分浏览器——例如,CSS 形状(第十九章)目前正符合这一描述。
因此,我在编写这本书时,按照实现的顺序而非推荐状态的顺序进行排列。早期章节讨论了在所有浏览器中完全实现的特性(或者至少在本书发布时应当实现);后续章节讨论了仅在某些浏览器中实现的特性——通常带有浏览器特定的前缀;而书的后面章节则涉及属性的潜在、猜测性或部分实现。
介绍语法
在介绍和解释完毕后,让我们进入 CSS3 的核心内容。整本书中,我会使用一种特定的语法约定来展示每一条新的规则和属性,格式大致如下:
*E* { property: *value*; }
在这个代码示例中,选择器用E表示。当然,在 HTML 中并不存在这个选择器;我只是用它来表示选择器不重要;这里可以使用任何选择器。
接下来是属性本身;在这个例子中,我使用了一个虚构的属性,称为property。接下来是该属性的值。对此,我使用了一个斜体别名来表示值,在这种情况下我称其为*value*。
如果一个属性接受多个值,我会列出每个值,并为其指定一个独特的别名。所以一个需要三个值的新属性可能会这样定义:
*E* { property: *first second third*; }
话虽如此,假设我们有一个名为monkeys的新属性(我一直想要一个monkeys属性),它只接受一个单一值。使用本书的语法,我会这样引入它:
*E* { monkeys: *value*; }
当我提供一个实际示例时,我可能会用一个有效的值来展示——比如一个数字值——像这样:
*E* { monkeys: 12; }
厂商前缀
当一个模块仍在积极评审中时,就像 CSS3 的许多部分一样,很多内容可能会发生变化;一个属性的语法可能会被修改,或者一个属性可能会被完全删除。有时,草案本身的措辞甚至有些模糊,且存在一定的解释空间。
同时,浏览器需要实现这些功能,以便我们能看到它们在实际中的效果。但如果两个不同的浏览器实现了相同的属性,却对其解释不一致,可能会出现哪些困难呢?你的代码结果可能会在每个浏览器中显得不同——甚至可能是极为不同的。为了防止这种情况的发生,每个浏览器厂商开始在实验性属性的前面加上一个短代码作为前缀。假设我们梦寐以求的monkeys属性已经在一个规范中新定义,所有主要的浏览器厂商都决定实现它,看看它如何工作。在这种情况下,你会使用以下代码:
E {
-moz-monkeys: value; /* Firefox */
-ms-monkeys: value; /* Internet Explorer */
-webkit-monkeys: value; /* Chrome/Safari */
}
重复的次数可能看起来有些不必要,但重复是为了我们自己的好处;你最不希望的事情就是所有浏览器对monkeys属性的实现各不相同,导致完全的混乱。
尽管出于好意,使用厂商前缀却导致了许多问题——开发人员在生产网站中使用了它们,但在浏览器实现更改后没有及时移除它们。这反过来意味着浏览器厂商不得不永远支持实验性功能,以避免使用这些功能的网站出现崩溃。因此,Chrome 和 Firefox 现在正逐渐放弃使用前缀属性,而是更倾向于实现新的功能,这些功能默认是禁用的,必须由开发人员主动启用,直到它们稳定到足以广泛使用为止。话虽如此,仍然有很多带前缀的属性存在,我会在本书中指出何时必须使用它们。
开始吧
这些应该是你开始阅读本书所需要的所有内容——当然,除了好奇心。我需要在 CSS3 上覆盖很多内容,所以我会比较快速地进行讲解,但每一章都应该能为你提供必要的知识,让你能够创建自己的测试、演示和网站,充分利用 CSS3 提供的灵活性和丰富的功能。
我们将从其中一个最简单的——同时也是最具颠覆性(而且我指的是正面的颠覆)——新特性开始:媒体查询。
第二章:2
媒体查询

在万维网仅能通过桌面或笔记本电脑上的浏览器访问的时代,编写 CSS 相对简单。尽管你必须考虑跨浏览器和跨平台的问题,但至少你可以合理地确定每个人使用的是功能上相似的设备来查看你的网站。然而,在过去的几年里,我们见证了访问网络的新设备爆炸式增长——从游戏主机到智能手机和平板电脑等移动设备。当用户可能通过宽屏桌面显示器或窄小的手持屏幕查看你的网站时,以相同方式展示内容已不再合理。
CSS 已经有了一种方法,通过link元素的media属性为不同的媒体类型提供不同的样式:
<link href="style.css" rel="stylesheet" media="screen">
但这种方法存在一些缺陷——其中之一就是,当屏幕尺寸介于 3.5 英寸到 32 英寸之间时,使用这种方法就像拿着一把相当钝的工具。类型列表过于宽泛,许多设备无法支持其目标类型——例如,我不知道有哪一款支持tv类型的网络电视。正因如此,W3C 已经开始逐步弃用媒体类型的使用。
解决这个问题的 CSS3 方法是使用媒体查询,它在媒体查询模块中有所定义(www.w3.org/TR/css3-mediaqueries/)。这些查询通过提供查询语法来扩展媒体类型,使你能够为用户的设备提供更加具体的样式,给用户带来量身定制的体验。这个描述可能听起来相当枯燥,但这个特性实际上是整个 CSS3 规范中最具革命性的一项。媒体查询让你能够自由地制作真正独立于设备的网站,无论用户如何访问你的网站,都能为他们提供最佳的体验。
媒体查询模块已经获得了 W3C 推荐状态,因此被视为标准。该模块在所有主流浏览器中得到了良好的实现,包括从版本 9 开始的 Internet Explorer。
媒体查询的优势
作为媒体查询强大灵活性的快速展示,我将展示一个如何优化网站以适应移动浏览器的示例,而不需要额外大量开发。
访问你网站的用户在移动设备上可能会遇到使用困难:文本可能显示得太小,放大后需要大量滚动才能找到导航元素;这些导航元素可能涉及下拉功能,通常需要通过鼠标悬停来触发,而这种操作在移动设备上常常不存在;大图像可能在弱数据连接下需要较长时间下载,并且会占用用户每月带宽的较大一部分。一些网站通过提供适配移动设备的版本来应对这些问题,但这通常需要大量开发工作。必须设置一个子域名,并为其提供与母站不同的样式表和 HTML 模板;图像需要调整大小以更好地适应小屏幕;还必须创建一个脚本来检测是否正在使用移动浏览器,并据此重定向到移动站点。这种方法可能会引发问题:你的脚本必须与所有移动浏览器版本保持同步,维护时往往需要复制工作,以确保移动版和桌面版同步更新。
媒体查询解决了许多问题。首先,它们基于设备的属性来检测设备,因此不需要使用浏览器嗅探脚本。它们允许你直接根据设备的功能来指定样式表,因此如果检测到一个小屏幕设备,CSS 规则将针对该屏幕大小进行调整,移除多余的元素,提供更小的图片,并使文本更加清晰。
例如,看看技术网站 The Next Web (thenextweb.com/),如图 2-1 所示。

图 2-1:The Next Web 网站在桌面浏览器和移动浏览器(内嵌图)中的显示效果
当在桌面浏览器中查看时,网站具有长的水平顶部导航栏,页面左侧是相关内容,主要内容则以网格布局显示。通过媒体查询的强大功能,当你在一个更窄的浏览器中查看同一网站时——例如 iPhone 使用的浏览器——导航栏变得更紧凑,分享选项被隐藏,相关内容被移出了屏幕,页面上的主要内容则移入了单列布局,这对于向下滚动来说是理想的。
当然,现在不仅仅是桌面和智能手机设备在浏览网页,我们真的需要朝着一个共同目标努力——即使网站能够在任何设备上进行优化。有关此方面的更多信息,请参见第 10 页中的“响应式网页设计”。
如果你想看看其他人如何使用媒体查询,你可以在网上找到一个很棒的画廊,地址是 www.mediaqueri.es/,它展示了一些媒体查询的优秀应用案例。
语法
媒体查询设置了一个参数(或一系列参数),当用于查看页面的设备具有匹配该参数的属性时,相关的样式规则将被启用。你可以通过三种方式使用媒体查询,所有这些方法都与 CSS 应用于文档的不同方式相对应。第一种方式是使用 link 元素来调用外部样式表:
<link href="file" rel="stylesheet" media="logic media and (expression)">
响应式网页设计
2010 年,Ethan Marcotte 写了一篇名为“响应式网页设计”的文章(* www.alistapart.com/articles/responsive-web-design/*),在文中他巧妙地整合了当代的思考,提出了通过媒体查询的力量,让网站能够适应不同设备的使用需求。以下是他所说的内容:
现在,比以往任何时候都更加重要的是,我们正在设计那些旨在适应不同体验梯度的作品。响应式网页设计为我们提供了一条前进的道路,最终让我们能够“为事物的潮起潮落设计”。
从那时起,响应式设计迅速成为了主流;大多数开发者都采用这种思路,每年都会有越来越多的网站使用或重新推出响应式设计方法。虽然这种设计并非没有挑战——特别是设计流式、响应式网站时必须重新思考,因为大多数设计工具无法胜任这一任务——但我们可以肯定地说,我们正在朝着打造一个任何人、任何地方都能访问的网络前进,而且这种设计已经经过精心考虑,能够与任何设备兼容。
第二种方法是使用 @import 指令来调用外部样式表:
@import url('file') logic media and (expression);
第三种方式是使用媒体查询在嵌入式样式元素中或在样式表本身中,通过扩展的 @media 规则进行:
@media logic media and (expression) { rules }
这个方法是我在本章余下部分将使用的方法,因为它在演示时更为清晰。你选择哪种方法主要取决于你个人的偏好和现有样式表结构的需求。
现在我已经介绍了声明方法,接下来我们来探讨一下语法。你应该已经熟悉 media 属性——它声明了样式应用的媒体类型,就像 HTML 中的 link 标签一样:
<link href="style.css" rel="stylesheet" media="screen">
最常见的媒体类型值是 screen 和 print,和当前的语法一样,你可以使用逗号分隔的列表来选择多个媒体查询(尽管随着其他媒体类型逐渐被淘汰,这种做法变得不太必要)。如果省略,媒体类型默认为 all,因此如果你编写的规则适用于所有媒体类型,你就不需要在媒体查询构造函数中指定它们;在这种情况下,这些示例在功能上是相同的:
@media all and (expression) { rules }
@media (expression) { rules }
注意
为了使本书其余部分的代码示例更加简洁,我会省略那些不重要的媒体类型。
@media 规则的第一个新属性是 *logic*。这个可选关键字的值可以是 only 或 not:
@media only media and (expression) { rules }
@media not media and (expression) { rules }
only值主要在你想隐藏规则以防止旧浏览器不支持该语法时使用;对于支持该语法的浏览器,only会被有效地忽略。not值用于否定查询;如果你设置的参数不满足条件,你就使用not来应用样式。
如果你在查询中使用了*逻辑*或*媒体*,还需要像前面的示例一样使用and运算符,将它们与所需的*表达式*属性结合使用。这个属性用于设置提供超越媒体类型功能的参数。这些参数被称为媒体特性,它们是媒体查询功能强大的关键。既然如此,让我们详细探讨一下它们。
媒体特性
媒体特性是关于用于显示网页的设备的信息:它的尺寸、分辨率等等。这些信息用于评估*表达式*,其结果决定应用哪些样式规则。这个*表达式*可以是,例如,“仅在宽度超过 480 像素的设备上应用这些样式”或“仅在设备横屏时应用这些样式”。
在媒体查询中,大多数媒体特性表达式都要求提供一个值:
@media (feature: value) { rules }
这个值是构建我刚才提到的示例表达式所需要的。然而,在一些情况下,你可以省略值,只需测试媒体特性本身是否存在于表达式中:
@media (feature) { rules }
随着我逐步讲解不同的媒体特性并解释何时需要或可选值,表达式会变得更加清晰。
了解了语法之后,让我们来认识一些更重要的媒体特性。接下来我介绍的这些是最适用于用于访问网页的彩色显示屏的特性,也是你日常使用中最可能用到的。其他媒体特性也有,但你更可能在替代设备(如电视或固定网格终端)上使用它们(前提是这些设备上支持这些特性)。
宽度和高度
width媒体特性描述了指定媒体类型的渲染视口的宽度,实际上通常指的是桌面操作系统中当前浏览器的宽度(包括滚动条)。基本语法要求一个长度值:
@media (width: 600px) { rules }
在这种情况下,规则仅应用于宽度恰好为 600px 的浏览器,这可能过于具体。width还接受两个前缀中的一个,max-和min-,这使得你可以测试最小或最大宽度:
@media (max-width: 480px) { rules }
@media (min-width: 640px) { rules }
第一个查询适用于宽度不超过 480px 的浏览器,第二个适用于宽度至少为 640px 的浏览器。
让我们来看一个实际的例子。在这里,我将利用浏览器窗口的尺寸为更宽的窗口提供一个装饰性的标题(为清晰起见,一些规则被省略了):
@media (min-width: 400px) {
h1 { background: url('landscape.jpg'); }
}
这个媒体查询测试的是至少 400px 宽的浏览器视口,并在这种情况下将背景图片应用于 h1 元素。
如果我的浏览器窗口宽度至少为 400px,我就能看到图片;如果我将窗口缩小,则只会显示文本标题。你可以在图 2-2 中看到这个例子。

图 2-2:在桌面浏览器和移动端(插图)中应用不同的样式规则,使用 *width* 媒体特性
height 媒体特性与 width 相同,只不过它是根据浏览器的高度而不是宽度进行判定。其语法与 width 相同,也允许使用 max- 和 min- 前缀:
@media (height: value) { rules }
@media (max-height: value) { rules }
@media (min-height: value) { rules }
然而,由于垂直滚动的普遍性,height 的使用频率远低于 width。
像素比
一般来说,CSS 像素单位(px)是计算机屏幕上单个像素的测量单位——如果你的浏览器视口宽度为 1024 像素,并且你给元素设置宽度为 1024px,那么你期望它能够填满视口的水平长度。然而,许多新设备,尤其是智能手机和平板电脑,拥有超高分辨率的屏幕,这会导致一个宽度为 1024 像素的元素在显示时看起来非常小,并且难以阅读。
为了应对这一问题,这些较新的设备通常拥有一个虚拟的 CSS 像素,独立于设备的物理像素,使得可以对内容进行放大和缩小,并且在小屏幕上保持高图形清晰度。物理像素与 CSS 像素的比率被称为设备像素比(DPR)。例如,iPhone 5S 的 DPR 为 2,这意味着一个 CSS 像素等于 4 个物理像素——水平和垂直各 2 个。
你可以在图 2-3 中看到这一点。左边的例子展示了在一个“正常”屏幕上的 CSS 像素,像素比为 1:1。中间的例子展示了在一个具有 DPR 为 2 的屏幕上的相同 CSS 像素,类似于 iPhone;在同一空间内有 4 个物理像素。最后,右边的例子展示了在一个 DPR 为 3 的屏幕上的效果,类似于 Nexus 5;现在在一个 CSS 像素的空间内有 9 个物理像素。

图 2-3:一个像素比为 1:1 的 CSS 像素(左),DPR 为 2(中),和 3(右)
实际上,这意味着,虽然 iPhone 5S(例如)具有 640×1136 的物理分辨率,但它的 CSS 分辨率为 320×568——正好是物理分辨率的一半,因为每个 CSS 像素等于两个物理像素,水平和垂直方向上都是如此(但仅在设备处于“移动模式”时有效;关于这一点,请参见 “设备宽度和高度” 在 第 15 页 的解释)。
尽管这种高 DPR 使得可缩放内容(例如文本和矢量图形)在高分辨率屏幕上清晰锐利,但位图图像在高分辨率屏幕上查看时可能会因质量丧失而受到严重影响。为了解决这个问题,可以使用一种新的媒体特性 resolution,它允许你根据设备的 DPR 来进行目标定位:
@media media and (resolution: value) { rules }
resolution 的值是一个分辨率单位的数字:每英寸点数(DPI)、每厘米点数(DPCM),或者最适合我们的 每像素点数(DPPX)。DPPX 单位映射到设备的 DPR,因此要应用一个规则到 DPR 值为 1.5 的设备,你可以使用如下规则:
@media (resolution: 1.5dppx) { rules }
与其他媒体特性一样,你还可以检测最大和最小像素比:
@media (max-resolution: number) { rules }
@media (min-resolution: number) { rules }
这种灵活性使得为具有更高像素密度的浏览器提供更高分辨率的背景图像变得更加容易,正如你在这段代码中所看到的:
➊ E { background-image: url('image-lores.png'); }
➋ @media (min-resolution: 1.5dppx) {
background-image: url('image-hires.png');
➌ background-size: 100% 100%;
}
第一条规则(➊)意味着,具有“标准”(或低分辨率)像素比的设备上的浏览器将使用标准图像(image-lores.png),而至少具有 1.5 DPR 的设备将使用高分辨率图像(image-hires.png)(➋)。注意这里使用了不太常见的 background-size 属性(➌);此属性应与高分辨率图像一起使用,以确保它们不会显示得比应用它们的元素更大(我在第八章中详细介绍了 background-size)。
Chrome、Firefox 和 Internet Explorer 10 以上版本都支持 resolution 媒体特性,尽管 IE 不幸没有实现 DPPX 值;为了兼容 IE,你应该使用 DPI,将所需的 DPR 值乘以 96(标准屏幕的 DPI 值)。这里有一个示例:
@media (resolution: 1.5dppx), (resolution: 144dpi) { rules }
Safari 不支持 resolution,而是使用一个名为 -webkit-device-pixel-ratio 的专有媒体特性(同时有 max- 和 min- 变体),它的值是一个没有单位的数字,表示目标 DPR。因此,为了兼容所有现代浏览器,可以使用以下规则:
@media (resolution: 1.5dppx), (resolution: 144dpi), (-webkit-device-pixel-ratio: 2) { rules }
resolution 规则是在 2012 年底由 WebKit 引擎实现的,因此我对它在撰写本文时(近两年后)仍未在 Safari 中发布感到失望。希望这个疏忽能够尽快得到纠正。
设备宽度和高度
width 和 height 媒体特性与浏览器视口的尺寸有关,但该视口并不总是与显示它的屏幕一样大。如果你需要针对物理屏幕大小而不是视口大小,可以使用 device-width 和 device-height 属性以及它们相关的 min- 和 max- 变体。你不会太常用到这些,但为了说明原因,我需要稍作偏离。
在上一节中,我解释了 CSS 像素和物理像素之间的区别。width 媒体特性是以 CSS 像素为单位测量的,而 device-width 以物理像素为单位。为了让内容在小屏幕上可读并呈现“自然大小”,两个维度需要匹配。你可以通过将 viewport meta 标签 添加到文档的头部来实现,像这样:
<meta name="viewport" content="width=device-width">
当页面的head部分存在带有这些值的视口元标签时,移动浏览器会进入“移动模式”,此时视口的大小会调整为适合该设备的理想尺寸。结果是,内容以更合适的尺寸显示在设备上。
注意
有关移动视口和像素的更深入解释,请参阅荷兰开发者 PPK 的《像素不是像素》(www.quirksmode.org/blog/archives/2010/04/a_pixel_is_not.html)。
移动设备浏览器的视口通常和屏幕本身一样大,因此它们基本上是等效的;而在桌面浏览器上,你通常希望使内容相对于视口的宽度,而不是屏幕的宽度进行调整。由于这些原因,device-width媒体特性相比width变得不那么有用了,实际上你可能不会经常使用它。
视口元标签正在被 CSS 标准化为@viewport规则;请参阅第 252 页的“设备适配”简要介绍。
方向
如果你不太关心查看设备的实际尺寸,但希望优化页面以适应横向(如典型的桌面/笔记本浏览器)或纵向(如手机或电子书阅读器)的查看方式,你需要使用的媒体特性是orientation。其语法如下:
@media (orientation: value) { rules }
*value*可以是两个关键字选项之一:landscape或portrait。当浏览器的宽度大于高度时,使用landscape值;当高度大于宽度时,使用portrait值。尽管orientation当然也可以应用于桌面浏览器,但你会发现它在处理用户可以轻松旋转的手持设备时最为有用,例如智能手机和平板电脑。
例如,你可以使用orientation根据访问者浏览器的方向来横向或纵向显示导航菜单。代码如下:
ul { overflow: hidden; }
li { float: left; }
@media (orientation: portrait) {
li { float: none; }
}
默认情况下,li元素的浮动值是left,使它们在页面上横向排列。如果相同的页面在portrait方向下查看——无论是通过将浏览器调整为比宽度更高,还是通过在设备上查看页面时设备处于纵向模式——浮动将被移除,li元素将垂直堆叠。你可以在图 2-4 中看到这个结果。

图 2-4:在移动浏览器中使用的*orientation*媒体特性:*portrait*(左)和*landscape*(右)*
由于orientation特性只有两个值,如果你使用一个值应用区分规则,那么另一个值自然就成为了对立面。在这个示例中,我只使用了portrait值,因此,默认情况下,所有不在该功能内的规则适用于landscape方向。
纵横比
你还可以创建查询,当达到特定的宽高比时应用这些查询。使用aspect-ratio来测试浏览器的宽高比,或者使用device-aspect-ratio来测试设备的宽高比。以下是这两个特性的语法:
@media (aspect-ratio: horizontal/vertical) { rules }
@media (device-aspect-ratio: horizontal/vertical) { rules }
*horizontal* 和 *vertical* 值是正整数,表示显示设备屏幕宽度和高度的比例,因此一个方形显示屏的比例是 1/1,而一个影院宽屏显示屏的比例是 16/9。
注意
一些设备——尤其是 iPhone——总是报告设备的竖屏方向的宽高比,即使在横屏显示时也是如此。
按比例选择可能存在一些陷阱。例如,一些设备制造商将宽屏定义为 16/9,一些定义为 16/10,还有一些定义为 15/10。并且设备可能没有准确的声明的比例;例如,iPhone 5S 宣称其比例为 16/9,但实际上显示的是稍大的 40/71 比例(竖屏方向)。使用max-和min-变体的aspect-ratio和device-aspect-ratio来应用规则可能更为可取。考虑以下代码,在其中查询的规则应用于任何具有大于 16/9 的宽高比的元素:
@media (min-device-aspect-ratio: 16/9) {…}
多个媒体特性
你也可以通过添加带有 and 操作符的表达式,将多个查询链在同一媒体类型上:
@media logic media and (expression) and (expression) { rules }
该语法测试这两个表达式是否匹配,然后再应用选定的规则。例如,要测试一个宽高比不大于 15/10 的设备上的窄屏,你可以使用此查询:
@media (max-device-aspect-ratio: 15/10) and (max-width: 800px) {…}
你还可以通过在以逗号分隔的列表中添加额外的查询,使用条件“或”表达式:
@media logic media and (expression), logic media and (expression) { rules }
当任何声明的情况为真时,应用这些规则;在以下示例中,规则应用于横屏方向的屏幕或竖屏方向的打印文档:
@media screen and (orientation: landscape), print and (orientation: portrait) {…}
当然,你还可以创建这些语法的任何组合。
移动优先网页开发
当今构建网站的常见最佳实践方法是采用一种被称为移动优先开发的方法,在这种方法中,我们首先为较小的屏幕开发,然后再为访问网站的较大设备的用户添加更大的资源和更多的复杂性。
采用这种方法的原因是因为某些浏览器加载页面资源(例如样式表中包含的图像)方式的问题。问题出现的原因是,一些早期采用媒体查询的开发者会,例如,给元素应用较大的背景图像,然后写规则将其从移动设备上隐藏:
E { background-image: url('huge-image.jpg'); }
@media (max-width: 600px) {
E { display: none; }
}
但这些背景图像,尽管被隐藏,仍然被浏览器下载并保存在缓存中,即使它们没有显示出来。这种方法增加了页面的加载时间,并消耗带宽配额——这些对于没有无线连接的移动设备用户来说都不好。
移动优先的页面创建方式是首先制定一个基础样式表,这个样式表会应用于所有浏览器,包括移动设备,然后逐步为具有更大屏幕的用户添加资源和功能,通过使用带有min-width特性的媒体查询来加载这些内容:
@media (min-width: 600px) {
E { background-image: url('huge-image.jpg'); }
}
这一变化意味着背景图像在小屏幕设备上永远不会被加载。这种方法可以推广到加载整个样式表:
<link href="basic.css" rel="stylesheet">
<link href="desktop.css" rel="stylesheet" media="(min-width: 600px)">
当样式表以这种方式分开时,某些浏览器会优化样式表的加载方式;例如,在 Chrome 中,由于文件desktop.css不适用于屏幕宽度小于 600px 的设备,其加载会被延迟,直到其他更高优先级的资源被下载完成——这是一种非常有用的优化。
这种移动优先的方法适用于过去几年中的绝大多数浏览器;对于一些非常老旧的浏览器,它们将只能获取基本样式表,这对它们来说可能更好,因为它们无法处理我将在本书的其余部分教授的高级功能。
总结
它们的语法可能很简单,但媒体查询具有非凡的强大功能。随着近几年移动互联网的爆发,设计师和开发者们开始意识到,他们可以在不使用传统的浏览器探测或为移动设备创建完全不同的版本的情况下,根据用户需求量身定制内容。
响应式网页设计运动在过去几年中的崛起得益于媒体查询的强大功能,在短短的时间里,它们已成为网页开发者手中最强大的工具之一。通过仔细考虑和巧妙使用媒体查询,你可以创建完美适应用户需求的网页,无论用户通过何种方式访问互联网。
媒体查询:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 媒体查询 | 是 | 是 | 是 | 是 |
第三章:3
选择器

选择器是 CSS 的核心,尽管原始的 CSS1 规范只有 5 到 6 个,CSS2 通过增加 12 个进一步扩展了选择器的范围。CSS3 更进一步,几乎将可用的选择器数量翻倍。
选择器可以大致分为两类。第一类是直接作用于文档树中定义的元素(例如p元素和href属性);这一类包括类、类型和属性选择器。为了方便起见,我将这些归类为DOM 选择器。第二类包括伪选择器,作用于文档树外部的元素或信息(例如段落的第一个字母或父元素的最后一个子元素)。我在第四章中介绍了伪选择器—在这里我讨论的是 DOM 选择器。
CSS3 提供了三种新的属性选择器和一个新的组合器,即将其他选择器连接在一起的选择器,比如 CSS2 中的子选择器(>)。这些选择器在选择器级别 3 模块中定义(* www.w3.org/TR/css3-selectors/),它是W3C 推荐*并在浏览器中有广泛且稳定的实现。除非你特别需要支持 IE6,否则你可以立即开始使用这些 CSS3 选择器—许多网站已经在使用了。
属性选择器
属性选择器是在 CSS2 中引入的,正如你从名字中可以预期的那样,它们允许你基于元素的属性(如href或title)及其值来指定匹配的规则。CSS2 中定义的四个选择器是:
E[attr] {…} /* Simple Attribute Selector */
E[attr='value'] {…} /* Exact Attribute Value Selector */
E[attr~='value'] {…} /* Partial Attribute Value Selector */
E[attr|='value'] {…} /* Language Attribute Selector */
在继续讨论 CSS3 中的新选择器之前,快速回顾一下如何使用每个选择器是很有价值的。为此,我将使用以下标记,这是一个(非常简短的)联系人列表:
<ul>
➊ <li><a href="" lang="en-GB" rel="friend met">Peter</a></li>
➋ <li><a href="" lang="es-ES" rel="friend">Pedro</a></li>
➌ <li><a href="" lang="es-MX" rel="contact">Pancho</a></li>
</ul>
简单属性选择器将规则应用于定义了指定属性的元素,无论该属性的值是什么。因此,给定以下代码:
a[rel] { color: red; }
我的标记中的所有a元素都有rel属性,尽管它们具有不同的值。因此,在这种情况下,所有元素都会应用该规则。如果你想更具体一些,可以使用精确属性值选择器来定义一个值:
a[rel='friend'] { color: red; }
这段代码仅将规则应用于标记中的第二个a元素(➋),因为它只选择具有friend确切值的元素。如果你想选择两个具有该值的元素,你可以使用部分属性值选择器:
a[rel~='friend'] { color: red; }
这段代码查找rel属性中作为空格分隔的列表(在大多数情况下是一个单词)中的friend值,因此它将规则应用于元素➊和➋。
最后的选择器,即语言属性选择器,将规则应用于具有与选择器中的第一个参数匹配的属性的元素,而该属性的值是选择器中的第二个参数,并紧接着一个连字符。如果这听起来有些奇怪的具体要求,那是因为这个选择器实际上只用于匹配语言子代码。示例标记中有两个西班牙语名字,每个名字的lang属性都以es-开头,尽管一个是西班牙(es-ES),另一个是墨西哥(es-MX)。要选择这两个元素,可以使用以下代码:
a[lang|='es'] { color: red; }
这个选择器会选择所有lang属性值以es开头的元素,不论它们的国家值是什么——也就是说,元素➋和➌。你可以将这个选择器用于任何具有连字符分隔值的属性,但在绝大多数情况下,这些值将是语言代码。
注意
这里使用的属性名称并不是从规范中取的,而是来自 Eric Meyer 的书《CSS 口袋参考》(O’Reilly Media, 2011)。
CSS3 中的新属性选择器
你已经看到属性选择器在查找精确或部分值时有多么有用,但如果你需要更多的灵活性呢?CSS3 的新选择器提供了通过匹配属性值中的子字符串来实现灵活性的功能。这个特性使得它们特别适合用于应用规则到 XML 文档,因为 XML 的属性值通常比 HTML 更为多样化——尽管它们对 HTML 开发者也同样非常有用。
开始子字符串属性值选择器
第一个新的属性选择器——为了避免重复这么长的标题,我将其称为开始选择器——查找其所选属性以传递给它的字符串为开头的元素。它使用插入符号(^)来修改选择器中的等号。以下是完整的语法:
E[attr^='value'] {…}
这段代码在指定属性的开头查找提供的值。为了说明这一点,考虑下面的示例标记,它展示了一个包含三个项目的列表,每个项目都包含一个具有不同(虽然相似)title属性值的超链接:
<li><a href="http://example.com/" title="Image Library">Example</a></li>
<li><a href="http://example.com/" title="Free Image Library">Example</a></li>
<li><a href="http://example.com/" title="Free Sound Library">Example</a></li>
我将把这个选择器应用到示例标记中:
a[title^='image'] {…}
在这个例子中,规则将应用于第一个列表项中的a元素,因为title属性字符串以image这个词开头。然而,规则不会应用于第二个项目中的a元素,尽管它的title属性包含了这个字符串,但它并不是以这个字符串开头。同样,第三个字符串也不会应用,因为这个字符串不匹配。
注意
在 HTML 文档中,属性选择器的值不区分大小写;然而,在 XML 文档中,值是区分大小写的。
开始选择器在你想要为超链接添加视觉信息时特别有用。这里是一个典型的指向外部网站的超链接示例:
<p>This is a <a href="http://example.com/">hyperlink</a>.</p>
当你在浏览器中看到这个链接时,你无法立刻判断它是指向同一网站的页面还是外部 URI。然而,使用这个新属性后,你可以将协议(http)作为参数传递,并添加一个图标来清晰地标示外部链接:
a[href^='http'] {
background: url('link.svg') no-repeat left center;
display: inline-block;
padding-left: 20px;
}
结果如图 3-1 所示。

图 3-1:使用开始选择器应用的图标
你可以将这个扩展到涵盖更多的 Web 协议,其中一些—mailto、ftp和https—在以下示例中有所展示,并在图 3-2 中进行了说明。
a[href^='mailto'] { background-image: url('email.svg'); }
a[href^='ftp'] { background-image: url('folder.svg'); }
a[href^='https'] { background-image: url('lock.svg'); }

图 3-2:使用开始选择器应用的更多链接图标示例
当然,开始选择器也有很多应用,适用于alt、cite和title等接受更多详细值的属性。随着 HTML5 的引入和一系列新表单元素与属性的出现,这个选择器及其即将推出的兄弟选择器将变得更加有用。
例如,考虑提议中的datetime属性,它接受类似2015-03-14的日期字符串值:
<time datetime="2015-03-14">March 14/time>
这意味着你可以使用开始选择器来为所有满足给定年份值的元素应用样式,这在日历或归档应用中非常有用:
[datetime^='2015'] {…}
结束子字符串属性值选择器
我称之为结束选择器,它的工作原理与开始选择器完全相同——只是反过来!也就是说,你用它来选择以给定值结尾的属性。语法只不同一个字符:这次你使用美元符号($)来替换等号(=)。这里是完整的语法:
E[attr$='value'] {…}
让我们再看一下前一节中的标记示例,只不过这次我们应用了结束选择器并使用了一个新值:
a[title$='library'] {…}
这次规则适用于所有列表项,因为它们的title属性值都以字符串library结尾。
就像开始选择器一样,你可以使用这个选择器为超链接提供视觉清晰度。不过这次,你不再使用href属性开头的协议,而是使用文件类型后缀。下面的代码展示了许多流行文件类型扩展的规则:
a[href$='.pdf'] { background-image: url('pdf.svg'); }
a[href$='.doc'] { background-image: url('word.svg'); }
a[href$='.rss'] { background-image: url('feed.svg'); }
图 3-3 展示了这些规则的应用实例。

图 3-3:使用结束选择器应用的链接图标
若要使用 CSS2 实现这个效果,你必须为标记应用设置class值(例如class="pdf")。使用结束选择器的优点是,文件链接可以自动检测到,而不需要你应用特定的类。缺点是,有时文件类型的后缀并不在 URI 的末尾。但下一个新选择器帮助我们解决了这个问题。
任意子字符串属性值选择器
最后的新属性选择器——我称之为任意选择器——与前两个选择器的工作方式相同,但它会在指定的属性字符串内的任何位置搜索提供的子字符串值。这个选择器使用星号(*)字符。以下是新的语法:
E[attr*='value'] {…}
为了演示这个选择器,我将再次使用与开始和结束选择器相同的标记,只不过这次应用的是任意选择器:
a[title*='image'] {…}
这个规则应用于第一个和第二个列表项,因为它们的title属性中都包含文本字符串image,尽管该字符串在每个示例中的位置不同。
你可能会注意到,这个选择器与 CSS2 中的部分属性值选择器有些相似,事实上,在这个例子中,它们是可以互换的:
a[title~='image'] {…}
但是这两个选择器在一个重要方面有所不同。在示例标记中,使用 CSS3,你可以仅通过子字符串匹配此元素:
a[title*='im'] {…}
然而,部分属性值选择器要求输入一个匹配空格分隔列表中完整项的值——在示例中,这将是free、image或library——因此,使用 CSS2 选择器时,im值不会在标记中的任何地方匹配。
继续使用前两个属性选择器的示例,任意选择器对于在 URI 末尾带有参数的文件类型图标也非常有用。考虑这个相当典型的 URI:
<a href="http://example.com/example.pdf?foo=bar">Example</a>
如果你使用结束选择器并设置值为pdf,即使文件类型是 PDF,该元素也不会被识别为有效目标,因为该值并未出现在字符串的末尾。然而,使用任意选择器提供相同的值就能实现预期效果;.pdf子字符串值出现在指定的属性中,因此图标被应用。
a[href*='.pdf'] { background-image: url('pdf.svg'); }
这个选择器是三种新属性选择器中最灵活的,因为它可以匹配子字符串,无论它们在字符串中的位置在哪里。但这种额外的灵活性意味着在定义提供给选择器的值时必须更加小心;当你可以在字符串中的任何位置进行匹配时,简单的字母组合更容易出现——这也是我用它来搜索.pdf(文件扩展名)而不是pdf(常见缩写)的原因。
多个属性选择器
你还可以将多个选择器串联在一起,这让你可以非常具体。通过使用多个选择器,你可以创建规则来应用于具有为开始、结束和任何位置定义的值的属性。举个例子,假设你有指向两个文件的链接,这两个文件的名字完全相同,但位于不同的文件夹中:
<p><a href="http://example.com/folder1/file.pdf">Example</a></p>
<p><a href="http://example.com/folder2/file.pdf">Example</a></p>
如果你只想为第二个p元素指定一个规则,可以将一些选择器串联在一起:
a[href^='http://'][href*='/folder2/'][href$='.pdf'] {…}
这段代码寻找具有以http://开头、以.pdf结尾,并且其中包含/folder2/的a元素。这样很具体!
通用兄弟组合器
我们在 CSS3 中的最后一个新的 DOM 选择器是组合器,你会记得它意味着将多个选择器结合在一起。一般兄弟组合器是相邻兄弟组合器的扩展,后者在 CSS2 中就已引入。两者的语法只相差一个字符:
E + F {…} /* Adjacent Sibling Combinator */
E ~ F {…} /* General Sibling Combinator */
这两者的区别微妙但重要:相邻兄弟选择器选择在同一层级的文档树中,紧接着元素(*E*)后的任何元素(*F*),而一般兄弟选择器选择同一层级中任何被元素(*E*)之前的元素(*F*),无论它们是否紧邻。
如果你仍然感到困惑,我会通过一个例子来解释。让我们从这段 CSS 开始:
h2 + p { font-weight: bolder; } /* Adjacent Sibling */
h2 ~ p { font-style: italic; } /* General Sibling */
并将其应用到以下标记(为了清晰起见,已截断):
➊ <p>Next we're going to discuss…</p>
<h2>René Descartes</h2>
➋ <p>A highly influential French philosopher…</p>
➌ <p>He once famously declared:</p>
<blockquote>
➍ <p>I think, therefore I am.</p>
</blockquote>
➎ <p>However, this presumes the existence of the thinker.</p>
你可以在图 3-4 中看到结果。在 CSS 中,我使用相邻兄弟组合器将紧接着h2元素的p元素加粗——即元素➋。我还使用一般兄弟组合器将所有紧随h2元素的p元素设置为斜体,这适用于元素➋、➌和➎。

图 3-4:相邻兄弟和一般兄弟组合器的区别
段落元素➊和➍没有应用粗体或斜体规则。为什么呢?因为元素➊位于h2之前,而元素➍位于blockquote内,因此它们在文档树中的层级不同(低一层),所以都不受规则的影响。
为了在没有一般兄弟组合器的情况下仅在 CSS2 中将与h2元素处于同一层级的段落设置为斜体,你需要将所有p元素设置为斜体显示,然后为blockquote内的p元素添加额外的规则来覆盖继承:
p { font-style: italic; }
blockquote p { font-style: normal; }
你可能不会经常使用一般兄弟组合器,因为它的许多功能与基本的 DOM 选择器重叠。尽管如此,你仍然会发现有很多场合可以利用这个组合器节省一些代码(和时间)。
总结
尽管属性是 HTML4 的一个关键特性,但它们大多数只接受有限范围的值,因此许多属性其实并不需要我在本章中介绍的属性选择器。除了href属性,只有少数属性接受更为详细的值(如alt、class、id、rel和title等属性)。不过,正如我之前提到的,HTML5 引入了像datetime和pubdate这样的属性,使得你可以在选择器中更加灵活创意。
本章介绍的新选择器,以及之前版本的 CSS 中的选择器,提供了根据定义的元素和属性应用样式规则的方法。当然,有时仅通过样式化元素和属性并不足以满足需求。这时,你需要添加类或非语义元素,作为挂载样式的钩子。在第四章中,你将发现 CSS3 如何消除这种需求。
选择器:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 新的属性选择器 | 是 | 是 | 是 | 是 |
| 通用兄弟组合符 | 是 | 是 | 是 | 是 |
第四章:4
伪类和伪元素

第一版 CSS 规范(CSS1)引入了 伪类 和 伪元素 的概念。这些选择器作用于扩展(或超出)文档树的信息。伪类用于区分元素的不同状态或类型;这些状态包括——但不限于——提供链接状态信息的伪类::hover、:visited、:active 等等。伪元素则提供了访问元素子部分的能力,包括那些选择文本节点部分的伪元素;例如,:first-line 和 :first-letter。
上述选择器自第一版 CSS 规范以来就存在,但在 CSS2.1 中加入了更多选择器——尽管伪元素的支持直到最近才得到较好的实现。CSS3 在这些基础上进行了扩展,增加了更多的伪类选择器,并且对语法做了(轻微的)调整,以区分伪元素。
拥有更多遍历文档的方法的优势应该很明显:需要的样式挂钩更少。你可能已经熟悉这样的标记:
<ul>
<li class="➊first ➋odd">➌<span>L</span>orem ipsum</li>
<li>Lorem ipsum</li>
<li class="odd">Lorem ipsum</li>
<li class="➍last">Lorem ipsum</li> </ul>
该标记包含类名,用于描述每个元素在文档树中的位置:first (➊) 和 last (➍) 显示 li 元素是 ul 元素的第一个和最后一个子元素,odd (➋) 用于标记编号为奇数的 li 元素。第一个 li 元素的首字母周围包含了一个额外的 span (➌)。
当你想为交替元素添加样式、为第一个和最后一个元素设置不同的值,或者为文本节点的首字母添加特殊格式时,可以使用这样的标记。这种标记可能会削弱代码的清晰度和语义性,但在许多情况下,你需要它来为样式提供挂钩。
CSS3 的新方法允许你在不使用多余类和非语义元素污染标记的情况下,达到相同的视觉效果,从而使代码更加简洁和可维护:
<ul>
<li>Lorem ipsum</li>
<li>Lorem ipsum</li>
<li>Lorem ipsum</li>
<li>Lorem ipsum</li> </ul>
新选择器的另一个主要优势是,如果添加了新元素到标记中,类名无需更新以适应这些新元素,同时仍能保持顺序。这一变化使得 CSS 更接近其目标:内容与表现的分离。
结构性伪类
正如我在本章引言中所述,伪类提供了一种根据文档树中未指定的信息选择元素的方法。有各种子类型,其中最常见的是 结构性伪类。这些子类型用于选择那些无法通过简单选择器访问的元素。
例如,考虑以下标记:
<div>
<p>Lorem ipsum.</p>
<p>Dolor sit amet.</p>
</div>
两个p元素中的第一个是div元素的第一个子元素。从文档树中可以明显看出这一点,但文档树并没有提供任何可以仅对该元素应用规则的信息。正因如此,CSS2 引入了:first-child伪类:
E:first-child {…}
这个伪类允许你根据存在但未作为元素属性提供的信息进行选择——这正是伪类的作用。自从 :first-child在 CSS2 中引入以来,它一直是唯一一个这种类型的伪类。但是,CSS3 通过引入 11 个新的结构性伪类大大扩展了这一范围。
:nth- 伪类*
新的四个伪类是基于一个计数值,用于查找元素在文档树中的位置;对于这个计数,你可以使用语法:nth-*。注意,我在这里使用了星号代替多个不同的值,我将在本章的其余部分逐一介绍这些值。
:nth-*伪类的基本语法非常简单。默认情况下,n表示从 0 开始并按 1 递增的数字(1、2、3 等)。另一个整数可以作为乘数传递给它。例如,2n表示每个 2 的倍数(2、4、6 等),3n表示每个 3 的倍数(3、6、9 等),依此类推:
E:nth-*(n) {…}
E:nth-*(2n) {…}
E:nth-*(3n) {…}
第一个示例使用默认值n,因此将选择所有类型为*E*的元素;在实际应用中,这与使用简单的元素选择器相同。下一个示例选择每隔一个的*E*元素,最后一个示例选择每隔三个的*E*元素。
你还可以使用加法(+)和减法(−)的数学运算符。因此,2n+1选择每隔两个加一个的元素(1、3、5 等),而3n-1选择每隔三个减一个的元素(2、5、8 等):
E:nth-*(n+1) {…}
E:nth-*(2n+1) {…}
E:nth-*(3n-1) {…}
第一个示例选择除了第一个实例之外的所有*E*元素;这些计数将是 2、3、4、5,依此类推。下一个示例选择每个奇数编号的*E*元素(1、3、5 等)。最后一个示例,如前所述,选择序列中的元素:2、5、8 等。
还有两个特殊的关键字值,even和odd,你可以用它们分别替换2n和2n+1:
E:nth-*(even) {…}
E:nth-*(odd) {…}
最后,使用0n(即零)作为值也是可以的。它本身没有用途,但与数学运算符结合使用时非常有用,因为它允许你精确定位一个没有重复的单一元素。事实上,为了简洁起见,你只需要在数学运算符后提供值。例如,若要仅选择选择器列表中的第三个元素,以下两个值都是有效的:
E:nth-*(0n+3) {…}
E:nth-*(3) {…}
在基本语法讲解完毕后,让我们继续讨论这些伪类本身。
:nth-child() 和 :nth-of-type()
大多数新的结构伪类允许你根据元素在文档树中的位置(相对于其父元素的 -child)或其分类(-of-type)来选择元素。这些定义经常会有重叠,但它们之间也存在至关重要的区别。
这些伪类的最简单示例是 :nth-child() 和 :nth-of-type()。第一个,:nth-child(),根据元素在其父元素中所有子元素的总数中的位置来选择元素;:nth-of-type() 的计数则不基于所有子元素,而仅基于指定类型的元素。
➊ E:nth-child(n) {…}
➋ E:nth-of-type(n) {…}
➌ E:nth-child(2n) {…}
➍ E:nth-of-type(2n) {…}
在这个例子中,规则 ➊ 和 ➋ 是等效的,因为计数值(n)保持为默认值;这两者都仅选择类型为 *E* 的所有子元素。差异在后面的示例中得以显现:在 ➌ 中,:nth-child(2n) 选择所有类型为 *E* 的元素,计数包括所有同级元素,但仅限于那些偶数编号的元素。而在 ➍ 中,相比之下,:nth-of-type(2n) 仅从那些元素中选择偶数编号的类型为 *E* 的元素。
这些规则比起解释来,演示起来要容易得多。我将通过以下示例演示它们之间的区别(为了清晰起见,文本已被截断):
<div>
<h2>The Picture of Dorian Gray</h2>
<p>The artist is the creator…</p>
<p>To reveal art and conceal the artist…</p>
<p>The critic is he who can translate…</p>
</div>
在我的样式表中,我将使用这两条规则:
➊ p:nth-child(2n) { font-weight: bolder ; }
➋ p:nth-of-type(2n) { font-weight: bolder; }
你可以在图 4-1 中看到这两条规则的不同结果。在示例标记中,div 元素总共有四个子元素:一个 h2 和三个 p。规则 ➊ 中的 :nth-child(2n) 选择器将每第二个子元素(第一个和第三个段落)设为粗体,正如左边的框中所看到的那样。与右侧框中的规则 ➋ 应用效果进行比较;:nth-of-type(2n) 选择器忽略 h2 元素,并仅对三个类型为 p 的元素中的每第二个实例应用粗体样式——即,仅对第二个段落生效。

图 4-1:比较使用 *:nth-child()* 选择器(左)与 *:nth-of-type()* (右)的效果
如我之前所提到的,且你无疑能从之前的例子中推测出,:nth-child() 和 :nth-of-type() 有不少重叠之处,通常可以互换使用,正如我在下面的示例中所做的那样。
图 4-2 左侧的表格显示了伦敦的五天天气预报(温度单位为摄氏度—0°C 等于 32°F)。这些数据是在一月采集的—这里并非 总是 这么冷!我想传达的所有信息都在表格中,但如果没有行的定义,我发现表格很难读取。
现在,将这个表格与右侧的表格进行比较,见同一图 4-2。在这里,我使用了被称为 斑马条纹 的技巧来帮助眼睛沿着行移动,这使得数据对我来说更易读。

图 4-2:天气预报表(左)和优化格式化以提高可读性(右)。天气数据来自 bbc.co.uk/weather/
我通过一个简单的 CSS3 声明实现了这个技巧:
tbody tr:nth-of-type(even) { background-color: #DDD; }
在这个例子中,我本可以使用 :nth-child(),因为在标记中,tbody 的所有子元素都是相同类型的:tr。当每个子元素都是相同类型时,:nth-child() 和 :nth-of-type() 是可以互换使用的。
:nth-last-child() 和 :nth-last-of-type()
:nth-last-child() 和 :nth-last-of-type() 伪类接受与 :nth-child() 和 :nth-of-type() 相同的参数,只不过它们是从最后一个元素开始倒着计数,起到了反向计数的作用。例如,假设我想使用一些视觉简写,在我的天气表格中显示出第四天和第五天的预报比前几天更不确定。你可以在 图 4-3 中看到这种效果。

图 4-3:使用 *:nth-last-child()* 进行额外格式化
我在最后两行的字符上使用了 :nth-last-child() 伪类进行斜体处理(尽管再次强调,:nth-last-of-type() 在这个例子中同样适用),并传递了 -n+2 作为参数:
tbody tr:nth-last-child(-n+2) { font-style: italic; }
我使用了负值(-n)来递减计数,这样就能实现反向操作。由于 :nth-last-child() 和 :nth-last-of-type() 是从树的末端开始倒着计数,所以在这里使用负值使得计数向前进行!计数从表格中的最后一个 tr 元素开始,按倒序进行,因此倒数第一和倒数第二行是首先被计数的,因此被设置为斜体。这个过程可能看起来不太直观,但随着你深入遍历文档树,便会逐渐习惯。
:first-of-type, :last-child 和 :last-of-type
如果你查看 图 4-2 中的表格,你会注意到天气列的文本是左对齐的,而其他列是居中对齐的。我是通过使用 :first-of-type 伪类实现的,它类似于 CSS2 中引入的 :first-child 选择器,但在本章中你已经看到,它的类型和子元素的区别。
如你所知,:first-child 伪类是一个选择器,用来应用规则到其父元素的第一个子元素。然而,与 :nth-of-type() 一样,:first-of-type 更为具体,它仅应用于父元素中属于特定类型的第一个子元素。还有一对对应的伪类,:last-child 和 :last-of-type,正如你可能猜到的,它们分别选择父元素中的最后一个子元素或该类型的最后一个子元素。
在上一节的天气表格示例中,表格主体中每一行的标记结构如下所示:
<tr>
<th>Sun</th>
<td>Sunny</td>
<td>8</td>
<td>4</td>
<td>8</td>
</tr>
我想将第二列的内容左对齐,因此不能在这里使用 :first-child,因为第一个子元素是一个 th。相反,我使用 :first-of-type 选择器:
tbody td:first-of-type { text-align: left; }
我将展示两个额外的例子,以清楚地说明它们之间的区别。我将这两个例子应用到相同的标记片段(为了清晰起见,我已截断文本):
<div>
<h2>Wuthering Heights</h2>
<p>I have just returned…</p>
<p>This is certainly…</p>
<p>In all England…</p>
<h3>By Emily Bronte</h3>
</div>
在第一个例子中,我使用了 :first-child 和 :last-child,如下所示:
:first-child { text-decoration: underline; }
:last-child { font-style: italic; }
结果如图 4-4 所示。h2 元素是 div 的第一个子元素,因此应用了下划线样式。div 的最后一个子元素是 h3 元素,因此它被斜体化。所有这一切都很直接。

图 4-4:应用 *:first-child* 和 *:last-child* 选择器
现在让我们看看使用 :first-of-type 和 :last-of-type 选择器时的区别:
:first-of-type { text-decoration: underline; }
:last-of-type { font-style: italic; }
看一下图 4-5 中的结果。你会注意到三个元素——h2、h3 和第一个 p——都带有下划线。这是因为它们是该元素类型的第一个实例。同样,h2、h3 和最后一个 p 都被斜体化。这是因为它们都是该类型的最后一个元素;h2 和 h3 都是其类型的第一个和最后一个,因此两个规则都适用于它们。

图 4-5:应用 *:first-of-type* 和 *:last-of-type* 选择器
与所有 *-type 和 *-child 伪类一样,它们之间的区别是微妙的,有时最后一个子元素也可能是其类型的最后一个,因此这些选择器可以互换使用。但正如我刚才所示,它们有时会有不同的应用场景。
:only-child 和 :only-of-type
这两个伪类用于选择文档树中具有父元素但没有兄弟元素(:only-child)或没有相同类型兄弟元素(:only-of-type)的元素。与许多之前的伪类一样,这两个伪类的功能有很多重叠,但下面这个例子说明了它们之间的区别。请看以下样式规则:
p:only-of-type { font-style: italic; }
p:only-child { text-decoration: underline; }
然后将它们应用到这个标记中:
<h2>On Intelligence</h2>
<p>Arthur C. Clarke once said:</p>
<blockquote>
<p>It has yet to be proven that intelligence has any survival value.</p>
</blockquote>
你可以在图 4-6 中看到结果。

图 4-6:比较 *:only-child* 和 *:only-of-type*
两个 p 元素是其类型在文档树中的唯一元素,因此 :only-of-type 规则选择它们并将其斜体化。然而,blockquote 内的 p 元素也是其层级中的唯一子元素,因此它也适用于 :only-child 规则,应用了下划线样式。
使用 :only-of-type 允许你从其他元素中挑选出一个元素,而 :only-child 则要求该元素是孤立的。
其他伪类
除了本章中讨论的结构性伪类外,CSS3 还引入了许多伪类,允许你根据其他标准选择元素。这些包括链接目标、用户界面元素,甚至是一个逆选择器,允许你根据元素“不是”什么来选择它!
:target
在 Web 上,站点不仅仅是页面之间的链接,还提供指向特定元素的内部链接。一个 URI 可以包含对唯一 ID 或命名锚点的引用。例如,如果你在页面中有如下标记:
<h4 id="my_id">Lorem ipsum</h4>
你可以通过这个链接引用它:
<a href="page.html#my_id">Lorem</a>
:target伪类允许你在引用的 URI 被跟随时应用样式。在这个例子中,如果你想在 URI 被跟随时对h4元素应用样式,你可以使用:
#my_id:target {…}
一种流行的做法是直观地高亮显示内部链接的主题,以便为用户提供明确的提示。例如,考虑一下博客评论的标准模式,其简化的标记大致如下:
<div class="comment" id="comment-01">
<p>Thanks for this scintillating example!</p>
<p class="author">N.E. Boddy, April 13</p>
</div>
另一个相当常见的模式是包括指向单个评论的链接:
<p><a href="#comment-02">Latest comment</a></p>
使用:target伪类,你可以轻松高亮用户希望阅读的评论:
.comment:target { background-color: #DDD; }
图 4-7 显示了评论的两种状态:左侧是点击引用链接之前的显示状态,右侧是点击链接后的显示状态——链接所指向的元素由于:target选择器而显示不同的背景颜色。

图 4-7:使用*:target*伪类应用的高亮效果
:empty
:empty伪类选择没有子元素的元素,包括文本节点。考虑这个标记:
<tr>
<td></td>
<td>Lorem ipsum</td>
<td><span></span></td>
</tr>
如果你应用这个 CSS 规则:
td:empty { background-color: red; }
规则仅应用于第一个td元素,因为另外两个分别包含文本节点和子元素。
:root
:root伪类选择文档树中的第一个元素,这只有在你为 XML 文档添加样式表时才特别有用——在 HTML 中,根元素将始终是html元素。使用:root在 HTML 中的一个小优点是,你可以通过它赋予html元素更高的特异性,这在你需要覆盖简单类型选择器时可能会很有用:
html {…} /* Specificity: 1; */
html:root {…} /* Specificity: 2; */
假设你正在创建一个基础样式表,并希望在html元素上设置一个不应被修改的属性。在这种情况下,你可以使用类似如下的代码:
html:root { background-color: black; }
更高的特异性使得这条规则优先于应用于html元素的其他规则,意味着以下规则将被忽略:
html { background-color: white; }
但在大多数情况下,你不太可能需要在大多数情况下使用这个。
:not()
否定伪类:not()选择所有元素,除了那些作为参数值提供的元素:
E :not(F) {…}
这个规则选择元素*E*的所有子元素,除了类型为*F*的元素。例如,要为div的所有直接子元素上色,除了p元素,你可以使用:
div > :not(p) { color: red; }
为了查看 :not() 的有用性,假设你有以下标记:
<div>
<p>Lorem ipsum dolor sit amet…</p>
<p>Nunc consectetur tempor justo…</p>
<p>Nunc porttitor malesuada cursus…</p>
</div>
现在假设你想让所有子 p 元素都呈斜体,除了第一个。
要使用 CSS2 实现这一点,你将样式应用于所有的 p 元素,然后再应用一个额外的样式将第一个元素重置回其先前的状态:
p { font-style: italic; }
p:first-child { font-style: normal; }
使用 :not(),你可以将其简化为一个规则:
p:not(:first-child) { font-style: italic; }
传递给 :not() 的参数必须是简单选择器——因此,组合符(如 + 和 >)和伪元素(我在 “伪元素” 中讨论过,它出现在第 45 页)不是有效值。
UI 元素状态
与表单和用户输入相关的元素可以具有不同的状态;例如,它们可以被禁用或选中,可以通过设置属性值来实现:
<textarea disabled="disabled"></textarea>
<input checked="checked" type="checkbox">
CSS3 有三个 UI 状态伪类选择器,可以让你根据元素的当前状态应用规则:
:checked {…}
:disabled {…}
:enabled {…}
注意
HTML 没有 *enabled* 属性;未被禁用的元素按定义是启用的。
为了查看这些伪类选择器的效果,考虑以下样式规则:
input[type='text']:disabled { border: 1px dotted gray; }
input[type='text']:enabled { border: 1px solid black; }
我将这些规则应用于一个包含两个文本 input 元素的表单,其中一个有 disabled 属性(表单结构不太规范,因为没有为输入框添加标签,但为了清晰起见,我将标签省略了):
<form action="">
<fieldset>
<legend>UI element state pseudo-classes</legend>
<input type="text" value="Lorem ipsum" disabled>
<input type="text" value="Lorem ipsum">
</fieldset>
</form>
你可以在图 4-8 中看到结果。

图 4-8:禁用和启用元素状态
如你所见,禁用的表单元素具有灰色文本(这是浏览器自动完成的)和灰色虚线边框(这是我在样式表中设置的)。我为启用的元素设置了一个实心黑色边框。
我这里没有演示 checked 状态,因为大多数浏览器对 checkbox 输入框可以应用的样式规则有不同的解释。关于跨浏览器表单元素样式的全面概述,我强烈推荐来自 456 Berea Street 博客的《使用 CSS 样式化表单控件》(www.456bereastreet.com/lab/styling-form-controls-revisited/)
还有一个第四种 UI 元素状态,indeterminate,它在某些情况下使用;例如,一个单选按钮输入框所在的组中,如果没有选择任何输入项,它就会被视为不确定状态,progress 元素如果没有值也会被视为不确定状态。尽管某些浏览器已实现这一功能,但它尚未完全定义,其状态依然是不确定的(哈!)。
约束验证伪类
HTML5 引入了一个新的客户端验证表单的 API,称为 约束验证 API,它可以在表单内容提交到服务器之前,确定是否满足某些要求。约束验证 API 带来了与之相关的一系列新伪类。
注意
Mozilla 开发者网络 (MDN) 提供了一个关于约束验证 API 的优秀介绍,链接为 developer.mozilla.org/docs/Web/Guide/HTML/HTML5/Constraint_validation/。
在约束验证下,可以通过使用新的 required 属性将表单字段设置为必填:
<input type="text" required>
你可以根据表单字段是否为必填或可选,使用它们的伪类来设置样式:
:required {…}
:optional {…}
每个表单字段可以处于两种验证状态之一:有效或无效。如果没有应用任何特定的约束(无论是浏览器还是作者设定的),则表单字段默认为有效。如前所述,每个状态都有一个相应的伪类:
:valid {…}
:invalid {…}
警告
在用户尚未与表单互动的情况下,未满足约束条件的字段——例如,必填字段——将已经应用 *:invalid* 伪类规则。
最后,一些 HTML5 元素可以设置允许的值范围,可以通过使用 min 和 max 属性来实现。你可以根据当前值是否在范围内来设置这些元素的样式,使用的仍然是成对的伪类:
:in-range {…}
:out-of-range {…}
伪元素
像伪类一样,伪元素提供了文档树中未指定的信息。但与伪类使用像元素在树中的位置或状态这样的“虚拟”条件不同,伪元素更进一步,允许你对文档树中根本不存在的元素应用样式。
在 CSS2 中,四个伪元素是 :first-line 和 :first-letter,它们用于选择文本节点中的子元素,以及 :after 和 :before,它们允许你在现有元素的开始和结束位置应用样式。CSS3 并没有引入新的伪元素,但它稍微调整了定义,并引入了一种新的语法,以便将它们与伪类区分开来。在 CSS3 中,伪元素的前缀是双冒号 (::),如下所示:
::first-line {…}
::first-letter {…}
::after {…}
::before {…}
注意
单冒号语法仍然被接受,原因是为了向后兼容,尽管它已被弃用,今后不应该再使用。
::selection 伪元素
CSS3 选择器模块的早期版本包括了 ::selection 伪元素的定义。尽管它已被正式从该模块中移除,但它在桌面浏览器中得到了很好的实现(在移动浏览器中则较少实现)。::selection 用于对用户在浏览器中选择的元素应用规则(例如,选择文本节点的一部分):
::selection {…}
只有有限数量的属性可以通过 ::selection 应用:color、background-color 和 background 简写(但不能使用 background-image)。使用 ::selection,你可以像这样进行样式设置:
p::selection {
background-color: black;
color: white;
}
图 4-9 显示了系统本地的 ::selection 颜色(上方)与我使用 ::selection 伪元素应用的颜色(下方)的对比。

图 4-9:通过 *::selection* 伪元素应用的自定义颜色
::selection 伪元素在 Chrome、Safari 和 IE9+ 中实现,无需前缀,在 Firefox 中则需要使用 -moz- 前缀——因此,尽管它不再是 CSS3 规范的一部分,你依然可以放心使用它。
总结
新一系列的伪类(以及未来可能定义的任何伪元素)使得通过 CSS3 进行文档遍历比以往任何时候都更加灵活和强大,而且可以实现更加简洁和易于维护的标记。
基于 UI 状态和表单验证的伪类数量的增加,极大地扩展了用户反馈的范围,无需依赖 JavaScript。这对于 HTML5 来说尤为重要,因为它更加关注 Web 应用程序。
DOM 和属性选择器:浏览器支持情况
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 结构伪类 | 是 | 是 | 是 | 是 |
:target |
是 | 是 | 是 | 是 |
:empty |
是 | 是 | 是 | 是 |
:root |
是 | 是 | 是 | 是 |
:not() |
是 | 是 | 是 | 是 |
伪元素(:: 语法) |
是 | 是 | 是 | 是 |
| UI 元素状态 | 是 | 是 | 是 | 是 |
| 约束验证 | 是 | 是 | 是 | IE10^(*) |
::selection |
是 | 是 | 是 | 是 |
- 不支持 :in-range, :out-of-range
第五章:5
WEB 字体

本章介绍的特性是本书中最古老的,早在多年前的 CSS2 中就已引入——但由于浏览器厂商未实现,后来从 2.1 规范中被删除。如今,新一代浏览器重新激起了对改善网页设计师可用排版选项的兴趣,我个人也欢迎这些特性在 CSS3 中的回归。最重要的是能够指定用户系统中不存在的字体——通过使用 @font-face 方法——这使得设计师摆脱了多年来使用的“网页安全”系统字体的束缚。令人惊讶的是,这项功能自 1997 年起就在 Internet Explorer 中可用!
IE4 是第一个允许使用网页字体的浏览器,但它使用了专有格式,这使得其他浏览器无法效仿。微软后来将其格式提交给 W3C,提议作为标准,但与此同时,Firefox、Safari 和 Chrome 都支持不同的格式集(见 “字体格式” 在 第 52 页),因此后来版本的 Internet Explorer 最终也跟随了这一做法。
CSS 字体模块 Level 3 (www.w3.org/TR/css3-fonts/)已被列为候选推荐状态,大多数规范已在现代浏览器中实现(有一些例外,我稍后会提到),因此你可以认为这些特性是安全可用的。
@font-face 规则
要在页面上显示网页字体,首先需要使用 @font-face 规则来定义它们。此规则设置字体的名称和类型,并为浏览器提供字体文件的位置。以下是基本语法:
@font-face {
➊ font-family: FontName;
➋ src: ➌local('fontname'), ➍url('/path/filename.otf') ➎format('opentype');
}
我将稍微拆解一下这个语法。首先,我使用 font-family 属性(➊)为字体命名。这个属性你应该很熟悉,尽管在规则内部它的作用与在常规选择器的声明块中有所不同;在这里,它用于声明一个字体名称,而不是引用一个字体名称。就像在 CSS2.1 中的 font-family 属性一样,你可以使用多个以空格分隔的单词,只要将它们用单引号括起来。
注意
当你使用 *font-family* 定义字体名称时,你可以多次使用相同的名称——事实上,有时候你需要这么做。我稍后将在 “定义不同的字体” 中讨论原因,见 第 49 页。
接下来是 src 属性(➋),它告诉浏览器字体文件的位置。该属性接受几种不同的值:local(➌)使用源字体的名称来检查字体是否已经安装在用户的机器上;url(➍)提供字体的路径,如果该字体在本地不可用的话。我还包括了可选的 format(➎)提示,用于指定字体类型;在这个例子中,我使用了 OpenType,但还有更多类型,我将在 “字体格式” 中讨论,位于第 52 页。
我可以为 src 属性提供许多不同的值,使用逗号分隔这些值,正如我在代码示例中所做的那样。这利用了级联的优势,允许不同的后备值,这在后续的例子中会派上用场。
注意
记住,在字体栈中,浏览器将使用它遇到的第一个可加载的文件,而不是逐个读取所有文件并选择最新的文件。
要使用我刚定义的字体,我只需要像平常一样在字体栈中调用它的名称:
E { font-family: FontName; }
为了说明一个现实世界的例子,我将使用 Chunk 字体(可以从 www.theleagueofmoveabletype.com/fonts/4-chunk/ 免费下载)应用到一个 h1 元素上,使用 @font-face 规则。以下是我将在样式表中使用的代码:
@font-face {
➊ font-family: ChunkFive;
src: ➋local('ChunkFive'), ➌url('ChunkFive.woff') ➍format('woff');
}
➎ h1.webfont { font-family: ChunkFive, sans-serif; }
第一步是给我的字体命名;我选择了 ChunkFive(➊),因为我容易记住它,但我可以使用任何名称。接下来,我为 src 属性提供值:local(➋)使用字体的真实名称 ChunkFive 来检查它是否已安装在我的系统上。随后,我输入我想要使用的字体文件的相对路径(➌),最后,我将 woff 作为 format 的值(➍)。我将在 “字体格式” 中解释 WOFF 格式,见第 52 页。
注意
通常可以通过使用字体管理应用程序或右键点击字体文件查看字体信息,来找到字体的真实名称。
在最后一行(➎),我使用在 @font-face 规则中定义的名称值,将新定义的字体加入字体栈,并将其应用到所有 h1 元素,且这些元素具有 webfont 类。为了查看显示效果,这里有一个快速对比,使用以下标记:
<h1>Alas, poor Yorick!</h1>
<h1 class="webfont">Alas, poor Yorick!</h1>
你可以在 图 5-1 中看到输出结果。

图 5-1:ChunkFive 字体(底部行),通过*@font-face*规则调用,与页面默认字体(顶部行)进行对比
定义不同的字体样式
你在本章看到的 @font-face 语法非常简单,但它只定义了一个字体样式——也就是一种权重、倾斜等的排列方式。如果你想使用不同的样式,比如加粗的权重或斜体类型,你必须为每个字体样式单独定义。为此,你可以重复使用相同的名称并在 @font-face 规则中添加额外的描述:
@font-face {
➊ font-family: 'Gentium Basic';
src: url('➋GenBasR.woff') format('woff');
}
@font-face {
➌ font-family: 'Gentium Basic';
➍ font-style: italic;
src: url(' ➎GenBasI.woff') format('woff');
}
h1 { font-family: 'Gentium Basic', sans-serif; }
在这里,你可以看到第一个@font-face规则将字体名称定义为Gentium Basic(➊),并给出了常规字体的 URL(➋)。第二个@font-face规则使用相同的字体名称(➌),但添加了font-style属性并将其值设置为italic(➍),URL 指向该字体的斜体字形(➎)。斜体样式会自动并恰当地应用,而不需要像此示例标记那样在 CSS 中额外定义:
<h1>I knew him, Horatio</h1>
<h1><em>I knew him, Horatio</em></h1>
第二个h1元素使用了之前规则中定义的斜体字形(你可以在图 5-2 中看到结果)。

图 5-2:使用@font-face应用的 Gentium Basic 常规字体(上)和斜体字体(下)
你可以通过在@font-face规则中使用不同的字体属性来定义字体的多个变体:font-weight用于设置不同的字重,font-variant用于小型大写字形,等等。
真正的字体与人工字体
使用网页字体时要注意的一点是,必须为你希望使用的每个不同字体字形定义一个适当文件的链接。如果没有,浏览器会尝试人为地重新创建字体字形,通常会产生难看的效果。
例如,如果你打算在页面中使用斜体样式,你必须确保在@font-face中也定义了斜体样式。下面是一个如何不定义斜体权重的示例:
@font-face {
font-family: 'Gentium Basic';
src: url('GenBasR.woff') format('woff');
}
h1 {
font-family: 'Gentium Basic', sans-serif;
font-style: italic;
}
你可以看到,我的@font-face规则使用了 Gentium Basic 字体的常规字形,但h1元素声明了一个斜体样式。你可以在图 5-3 中看到该效果,该图将这种方法与上一节中正确定义的方法进行了比较。

图 5-3:比较人工斜体(上)和真正的斜体字体(下)
如你所见,这两个示例差异很大。第一个是将选定的 Gentium Basic 字体倾斜,伪造斜体样式(使用第一个代码示例);字符更大,稍微变形,并且间距不一致。第二个是正确的斜体字体(使用正确的方法),它使用专门为此目的设计的字符。
同样的原则适用于所有不同的字体:粗体、斜体、粗斜体、小型大写字母、压缩字体等等。
“防弹”@font-face 语法
我在本章开头解释过,@font-face规则已经存在相当长时间,从 1997 年起便在 Internet Explorer 中实现。这意味着它带有一些不幸的遗留问题,特别是在旧版本的 IE 中。此外,一些与字体格式相关的历史性问题也可能在旧版本的其他浏览器中引起兼容性问题。
由于这些问题,你需要找到一种解决方法,确保@font-face在所有浏览器中都能正确工作。在介绍完全跨浏览器的“防弹”@font-face语法之前,让我简要讨论一下它解决的一些问题。
使用本地字体
src 属性中的 local() 值用于检查用户的系统中是否已经安装了指定的字体——如果用户已经安装了该字体,则可以应用本地副本,而无需重新下载。local() 是一个不错的想法,但它也有一些缺点。第一个缺点,也是最重要的,就是local()在任何低于 9 版本的 Internet Explorer 中都不被支持!
另一个缺点是,在某些情况下,@font-face 规则与字体管理软件兼容性差,可能会显示错误字符,或者弹出对话框请求权限使用字体。
由于这些原因,通常省略 local() 值会更安全。
字体格式
下一个问题是不同且互相竞争的格式。当@font-face最初被实现时,它只支持微软的专有嵌入式开放类型(EOT)格式,并且这是 IE8 及以下版本唯一支持的字体格式。更复杂的是,当浏览器进入兼容模式时,IE9 会导致@font-face规则失效;这个问题非常特殊,且随着时间的推移变得越来越不相关,但值得注意的是,我们可以通过在防弹语法中简单修复它。
对网页字体的兴趣重新激增,原因是现代浏览器——最初是 Safari,然后是 Firefox,再后来是其他浏览器——允许使用更常见的TrueType和OpenType格式。不幸的是,许多商业字体厂商不允许以这种方式使用他们的字体,因为这会让他们的字体被非法复制变得更容易(详见 “网页字体授权” 在第 53 页)。因此,Mozilla 与一些字体制造商进行了咨询,创建了Web Open Font Format (WOFF),现在所有主流浏览器(包括 IE9 及以上版本)都支持此格式,除了旧版的安卓系统浏览器(4.3 及以下)。
一些浏览器还支持可缩放矢量图形(SVG)字体类型。这种字体类型是字体的矢量重建,文件大小大大减小,非常适合移动设备使用。然而,这种格式只有在你需要支持旧版 iPhone Safari(4.1 及以下版本)时才真的有用。
最终的“防弹”语法
为了确保你选择的字体在所有浏览器和平台中显示一致,你应该使用以下格式的代码:
@font-face {
font-family: 'Gentium Basic';
➊ src: url('GenBkBasR.eot');
➋ src: url('GenBasR.eot?#iefix') format('embedded-opentype'),
➌ url('GenBkBasR.woff') format('woff'),
➍ url('GenBkBasR.ttf') format('truetype');
}
让我来解释一下这里发生了什么。第一个指定的字体是为 Internet Explorer 8 及以下版本准备的 EOT 字体(➊)。这个字体有自己的规则,因为接下来的规则包含了可选的format()提示;这个提示对老版的 IE8 来说是陌生的,因此整个规则会被忽略。但是,为了处理 IE9 的兼容性问题,仍然需要再次包含 EOT 字体(➋)。接下来,定义了 WOFF 格式(➌),这是大多数浏览器使用的格式,之后是 TTF 格式(➍),用于较旧的浏览器,包括 Android 4.3 及以下版本(请记住,浏览器会忽略它们无法识别的格式,因此无法加载这些格式)。
由于 IE9 的兼容性问题变得不再那么重要,你可以根据自己的判断省略第二行(➋)。
为了让这个方法有效,主要的要求是你选择的字体必须有三种不同的格式。为了简化这一过程,我强烈建议使用 Font Squirrel 的@font-face生成器(www.fontsquirrel.com/fontface/generator/)。只需上传你想使用的字体文件,@font-face生成器会将其转换为所有相关格式,并生成你在页面中需要使用的 CSS 代码。这个工具非常有用。Font Squirrel 还提供了一个可以直接用于@font-face嵌入的字体库,省去了你转换字体的麻烦。
注意
如果你想了解为什么需要使用 IE 技巧以及它是如何工作的,请阅读 www.fontspring.com/blog/the-new-bulletproof-font-face-syntax/。
网页字体使用许可
如前所述,在第 52 页的“字体格式”一节中,许多字体厂商明确禁止使用@font-face将他们的网页字体嵌入到你的页面中。他们之所以禁止,是因为链接的 OpenType 或 TrueType 字体容易被找到并下载,然后在在线和离线应用中非法使用。为此,创建了 WOFF 文件格式;WOFF 是一种仅限网页使用的格式,并且可以包含许可信息,帮助追踪版权侵权者。许多字体厂商已经开始销售这种格式,我希望未来会有更多厂商跟进。
一般来说,最好的做法是检查你选择的字体是否有明确的许可证,允许你将其用于网页嵌入;不要假设某个字体是免费的下载,就意味着它可以免费在线使用。话虽如此,许多允许嵌入的高质量免费字体在线可用;一些资源在附录 B 中给出。
虽然字体许可的情况仍在变化,但许多网页字体服务提供商已经创建了合法嵌入字体的机制。通过在你的页面中添加 JavaScript,提供商就被授权从他们的网络提供字体文件,这样你就可以在你的字体栈中调用这些字体。这个方法被称为字体即服务(FaaS)。
大多数 FaaS 提供商是商业性的,允许免费使用有限的一组字体,但大多数字体需要支付月费或年费。这个领域中的两大主要玩家可能是 Fontdeck (fontdeck.com/)和 Typekit (typekit.com/)。其他提供商只提供免费字体——Google Fonts (www.google.com/fonts/)是一个显著的例子。每个提供商都有自己的方式将授权字体包含到你的网站中,通常是通过引入外部的 CSS 或 JS 文件,或者两者都用。
一个真实世界的网页字体示例
在讨论了字体的复杂性、许可和多种语法之后,让我们来看一个@font-face的实际示例。我将使用一个比较标准无衬线字体(Arial)和三种不同字体家族(均来自 Font Squirrel)显示的相同文本的示例。
这是这个示例的 CSS。请记住,为了清晰起见,我简化了这个代码片段,仅展示了单一的字体格式(WOFF),虽然附带的示例文件包含了完整的代码:
@font-face {
font-family: 'CartoonistHand';
src: url('CartoonistHand.woff') format('woff');
}
@font-face {
font-family: 'CartoonistHand';
font-style: italic;
src: url('CartoonistHand-Italic.woff') format('woff');
}
@font-face {
font-family: 'CartoonistHand';
font-weight: bold;
src: url('CartoonistHand-Bold.woff') format('woff');
}
@font-face {
font-family: 'ChunkFiveRegular';
src: url('Chunkfive.woff') format('woff');
}
@font-face {
font-family: 'AirstreamRegular';
src: url('Airstream.woff') format('woff');
}
.font-face h1 { font-family: ChunkFiveRegular, sans-serif; }
.font-face h2 { font-family: AirstreamRegular, cursive; }
.font-face p { font-family: CartoonistHand, sans-serif; }
我还省略了一些颜色和大小的调整,以使代码尽可能易读。以下是我使用的标记:
<h1>Great Expectations</h1>
<h2>By Charles Dickens</h2>
<p>My father's family name being <em>Pirrip</em>, and my Christian name
<em>Philip</em>, my infant tongue could make of both names nothing longer or
more explicit than <strong>Pip</strong>. So, I called myself <strong>Pip
</strong>, and came to be called <strong>Pip</strong>.</p>
你可以在图 5-4 中看到这个输出。

图 5-4:使用“网页安全”字体(左)和使用不同网页字体(右)的文本
在右侧的示例中,我混合了三种相当独特的字体家族——许多设计师可能会告诉你,在生产站点中混合字体并不是一个好主意,但这在此示例中能很好地阐明我的观点。不论你对我的字体选择有何看法,我希望你至少能同意,在应用了这些字体选择后,文本看起来更加生动和诱人。
尽管我们为了获得跨浏览器语法走了很长的弯路,但使用@font-face并不复杂;你会在声明所需的字体面和变体时花费一些额外的设置时间,但之后你可以像使用系统字体一样在字体堆栈中调用它们并进行样式化。
控制字体加载
网页字体作为外部资源加载,必须先由浏览器下载,才能显示。在文件加载之前,应用字体的元素上将看不到任何字体。你可能会看到在字体加载并应用时出现轻微的“闪烁”现象。这就是所谓的未样式化文本的闪烁(FoUT)。
许多 FaaS 提供商提供了使用配置文件解决这个问题的方法,但如果你自己托管字体,你可能会想要研究 Web Font Loader 库 (github.com/typekit/webfontloader/),它提供了一个事件系统,允许你在字体加载时动态控制页面的显示。
更多字体属性
CSS3 Web 字体模块不仅重新引入了 @font-face 规则;它还恢复了两个最早为 CSS2 提出的字体属性。这些属性对于让你精确控制字体非常有用——我之所以说 有用,是因为截至目前,它们并未广泛实现。
font-size-adjust
使用字体堆栈(font stacks)在 CSS 中的唯一缺点是字体的大小差异可能非常大;你选择的第一种字体在 16px 下可能看起来很好,但如果该字体不可用,下一个备选字体可能会显得更小,或者有不同的比例,使得在相同的大小下更难阅读。为了解决这个问题,font-size-adjust 属性允许你动态地调整 font-size 属性,以确保无论使用哪种字体,外观始终保持一致。font-size-adjust 属性接受一个小数值;以下是语法:
E { font-size-adjust: number; }
*number* 值是由小写 x 字符(即 x-height)所占的字体总高度的比例。换句话说,一种字体的总高度可能为 16px,但小写 x 的高度可能只有一半(8px),这就给出了一个 x-height 比例为 0.5(8 除以 16):
p { font-size-adjust: 0.5; }
通过使用 font-size-adjust,你可以确保无论显示哪种字体,x-height 始终保持相同的值,且可读性不受影响。为说明这一点,请考虑以下代码:
h1.adjusted { font-size-adjust: 0.517; }
h1.impact { font-family: Impact, serif; }
然后,在接下来的三个 h1 元素中,所有的 font-size 值相同,我通过它们的类名应用了不同的值,你可以在这段标记中看到:
<h1>Of most excellent fancy</h1>
<h1 class="impact">Of most excellent fancy</h1>
<h1 class="adjusted impact">Of most excellent fancy</h1>
第一个 h1 使用默认的 Helvetica Neue 字体渲染,第二个使用 Impact,第三个也使用 Impact,但应用了 font-size-adjust 属性,值为 0.517,即 Helvetica Neue 的 x-height。你可以在图 5-5 中查看效果。

图 5-5: *font-size-adjust* 对 Impact 字体的影响(第三行)
你可以清楚地看到在前两个 h1 元素中,Helvetica Neue(第一行)和 Impact(第二行)字体之间的差异。(当然,你不太可能在同一个字体堆栈中同时使用这两种字体,但由于它们的 x-height 差异很大,因此它们非常适合用于说明。)如我所提到的,Helvetica Neue 的 x-height 比例为 0.517,这意味着小写的 x 大约是字体高度的一半。相比之下,Impact 的 x-height 比例为 0.7,这意味着大写字母和小写字母之间的高度对比较小。
示例中的前两行没有做任何调整,因此第二行的 Impact 小写字母比第一行的 Helvetica Neue 要高得多——字体大小相同,但它们的度量标准不同。然而,在第三行,我将 font-size-adjust 的值设置为 0.517,以匹配 Helvetica Neue 的 x-height 比例:
h1.adjusted { font-size-adjust: 0.517; }
这调整了字体大小,使得 Impact 字体显示为更小的尺寸——略高于 18.6px,约为 font-size 设置的 36px 高度的一半。通过直接比较这两个元素,你可以更清楚地看到这一点,如图 5-6 所示。在这里,调整后的 Impact 字体中没有 上升部分(即超出 x 高度的部分)的字符——也就是说,e、x、c 和 n——与 Helvetica Neue 字体的字符高度完全相同。

图 5-6:左边为 Helvetica Neue 字体的字符,右边为使用 *font-size-adjust* 调整后的 Impact 字体的字符。
不幸的是,字体的 x 高度比例并不容易获得;你可以使用图形软件手动测量,或者尝试找到一个在线资源。(我找到了一款由在线字体服务 Fontdeck 制作的计算器,它与系统中安装的字体兼容:fontdeck.com/support/fontsizeadjust/。)
这个属性的主要缺点是,截至目前,Firefox 是唯一支持它的浏览器。
font-stretch
一些字体家族包含压缩或扩展的变体,font-stretch 属性可以让你访问这些变体。语法如下:
E { font-stretch: keyword; }
根据规范,关键字值可以是以下之一:normal(默认值)、ultra-condensed、extra-condensed、condensed、semi-condensed、semi-expanded、expanded、extra-expanded 和 ultra-expanded。每个关键字都与字体家族中的某个变体相关,如 Frutiger Condensed 或 Nova Ultra Expanded。
在以下示例中,我使用 font-stretch 显示了两种不同的 PT Sans 字体样式,使用的代码如下:
h1 { font-family: 'PT Sans', sans-serif; }
h1.narrow { font-stretch: condensed; }
h1 元素显示了两次,每次使用 PT Sans 字体。在第二次中,我使用了 font-stretch 属性,值为 condensed,这告诉浏览器显示 PT Sans Condensed 字体。你可以在图 5-7 中看到结果。

图 5-7:第二个示例由于 *font-stretch* 属性的影响,使用了狭窄的字体样式。
font-stretch 属性已在 Firefox 和 IE9 及以上版本中实现,但你也可以通过使用 @font-face 规则(在本章开始时介绍)来复现这一效果,指定字体堆栈中的压缩或扩展样式:
@font-face {
font-family: 'PT Sans Condensed';
src: url('PT-Sans-Narrow.woff') format('woff');
}
OpenType 特性
尽管 CSS3 在网页排版上取得了巨大的进步,但它仍然仅仅触及了字体可能性的表面。如果你将浏览器中可用的选项与桌面出版应用程序(如 Adobe InDesign)中的选项进行比较,你会发现后者比前者要丰富得多。
OpenType 字体格式不仅支持字体或粗细变化,还具有多种连字、修饰、特殊数字字符等功能。(如果这些术语对你来说没有意义,我推荐你阅读 Magnet Studio 的《OpenType 初学者指南》,链接地址为 www.magnetstudio.com/words/2010/opentype-guide/)。
启用字体特性
许多浏览器已经实现了一项功能,允许你探索 OpenType 和其他类似格式提供的额外特性。这个新属性叫做 font-feature-settings,下面是它的语法:
E { font-feature-settings: "parameters"; }
*parameters* 值是一个字符串序列,包含每个字体特性的简写代码,还可以加上一个可选的二进制值来启用或禁用该特性。下面是一个示例:
E { font-feature-settings: "dlig" on; }
第一个参数是 dlig,是可选连字的简写代码,值为 on 表示启用它——默认状态是启用的,因此在这个示例中,你可以安全地省略这个值。
如果你想禁用某个特性,可以使用替代的二进制值 off;在以下示例中,我禁用了 OpenType 的小型大写字母特性:
E { font-feature-settings: "smcp" off; }
如我所提到的,你可以使用多个参数——只需创建一个逗号分隔的列表。下面的示例启用了常见的连字,并禁用了表格数字:
E { font-feature-settings: "liga", "tnum" off; }
让我们来看一些示例,展示使用 OpenType 特性的优势。在《真正的字体与人工字体》的 第 50 页中,我展示了为什么你应该始终使用真正的斜体字体,而不是让浏览器人为地创建斜体。在这个示例中,你会看到同样的原则适用于使用小型大写字体变体。以下是相关的样式规则:
.smallcaps { font-variant: small-caps; }
.ot-smallcaps { font-feature-settings: "smcp"; }
这里,我使用了两个 h1 元素:第一个应用了带有 small-caps 值的 font-variant 属性;第二个使用了 font-feature-settings,并使用参数来切换小型大写字母(smcp)。你可以在 图 5-8 中看到差异。

图 5-8:下方示例使用了 OpenType 的小型大写字母特性
在第一个 h1 元素中,使用了模拟的小型大写字母,其比例不对;大写字母和小写字母之间的差异几乎无法察觉。与第二个 h1 元素相比,后者的比例更明显,视觉效果更加悦目。
现在,我将演示连字的使用,连字用于将某些字符对连接在一起,使其看起来更和谐。和字距调整一样,大多数浏览器会自动使用常见的连字,除非被特别指示不使用,所以在这个示例中,我将对比没有连字、常见连字和可选——也就是更多装饰性的——连字的文本。
下面是代码:
.lig-none { font-feature-settings: "liga" off; }
.lig-common { font-feature-settings: "liga"; }
.lig-disc { font-feature-settings: "dlig"; }
这段代码应用于三个h1元素。第一个使用参数字符串"liga" off,因此禁用了常见连字。第二个使用相同的参数字符串,只是不带off值,因此启用了常见连字。第三个使用字符串"dlig",启用了任意连字。图 5-9 对这三者进行了比较。

图 5-9:比较 OpenType 连字:(从上到下)无连字、常见连字和任意连字
请注意字符对Th和ct。在第一个h1元素中,没有连字,它们作为独立字符呈现。在第二个元素中,启用了常见连字,Th字符对在字符几乎接触的地方被连接起来。在第三个h1元素中,连字是任意的,因此ct字符对也被连在一起,并且有着夸张的装饰。
在撰写本文时,font-feature-settings已在 IE10+、Chrome 和 Firefox 中实现,并带有适当的厂商前缀。你也可以在 Safari 中访问这些属性,但方式稍有不同;继续阅读,了解如何实现。
注意
这些特性都是对浏览器的建议,而不是命令;一些浏览器会根据平台和潜在性能选择如何渲染字体,这些特性建议可能会被忽略。
字体特性属性
font-feature-settings启用或禁用的各个特性也被指定为作为单独的属性实现,这些属性被称为font-variant-*属性。例如,要处理连字,使用font-variant-ligatures属性,并设置代表所需连字的关键字值;以下代码禁用了任意连字:
E { font-variant-ligatures: no-discretionary-ligatures; }
你还可以使用font-variant-position来设置上标或下标,使用font-variant-caps来设置标题大写,使用font-variant-numeric来设置数字样式,使用font-variant-alternates来设置连笔、装饰等。
目前,只有 Chrome 和 Safari 浏览器支持font-variant-*属性,并带有-webkit-前缀,图 5-7、5-8 和 5-9 中的示例应当在 Safari 中使用这些属性,而不是font-feature-settings。
另一个相关的属性是font-kerning,它(显而易见)控制字体的字距调整。它接受normal值,表示应用字距调整;none值,表示禁用字距调整;以及auto(默认值),允许浏览器决定是否进行字距调整。
E { font-kerning: normal; }
在图 5-10 中,你可以看到这个属性的实际效果。上方的标题禁用了字距调整,而下方的启用了字距调整。我添加了一些背景线条以便更容易看到。

图 5-10:使用*font-kerning*属性禁用(上)和启用(下)字距调整
总结
尽管font-size-adjust和font-stretch未来肯定会派上用场,但目前@font-face是 Web 字体模块的杀手级功能。然而,@font-face也并非没有缺点,尤其是每使用一种额外的字体都会增加页面加载时间。此外,要注意,错误使用或过度使用不同的字体可能导致可读性下降。你的网站的成败取决于其内容,因此请确保你的访客能轻松阅读,通过仔细选择字体并在不同浏览器上仔细检查。
尽管有这些警告,你仍然可以使用这个简单的规则来达到惊人的效果。在第六章中,我将展示一些进一步增强排版的技巧。
Web 字体:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
@font-face |
是 | 是 | 是 | 是 |
font-size-adjust |
否 | 是 | 否 | 否 |
font-stretch |
否 | 是 | 否 | 是 |
font-feature-settings |
是 | 是 | 否 | IE10 |
font-variant-^(*) |
是* | 否 | 是* | 否 |
- 需要厂商前缀
第六章:6
文本效果和排版样式

自互联网诞生以来,文本内容一直是其支柱,然而多年来,我们只能依赖一套有限的工具。CSS3 通过在文本模块中引入一系列新的和更新的特性,极大地扩展了其排版工具集。
这些新特性中最重要的是能够为文本添加阴影。虽然这个功能听起来并不特别具有革命性——印刷排版师早已使用阴影——但新的语法足够灵活,可以实现一些非常漂亮的效果。另一个类似的功能是文本轮廓(或文本描边),尽管它并没有被广泛实现,但它确实增加了在创建装饰性标题时的选择多样性。除此之外,还有一些不那么引人注目的效果,但对于提升文本可读性却能发挥巨大的作用。
CSS 文本 3 级模块(www.w3.org/TR/css3-text/)目前处于最终草案工作版本状态;然而,一些元素已经得到了很好的实现,可以立即使用。
在我介绍这个模块中的第一个新属性之前,我将简要介绍坐标和轴的概念。如果你已经熟悉这些概念,可以跳过这一部分;否则,请继续阅读。
理解轴和坐标系
CSS3 中新引入的一个语法概念是轴(当你有多个时称为轴)。如果你还记得数学课,可能已经知道什么是轴,但如果你正在阅读这一部分内容,我假设你需要一些复习。
CSS 使用笛卡尔坐标系,它由两条线组成,一条是水平线,另一条是垂直线,两条线在直角处相交。这些线中的每一条都是一个轴:水平线称为x 轴,垂直线称为y 轴。这两条线交汇的点叫做原点。你可以在图 6-1 中看到这一点的示意。

图 6-1:x 轴 和 y 轴以及原点
对于屏幕上的项目,你可以以像素为单位测量这些轴的长度。在图 6-1 中,你可以看到轴和原点覆盖在一个网格上。假设每个方格对应一个像素。你还会注意到在每个轴的两端都有正(+)和负(−)标签;这些标签告诉你,从原点起的距离将沿着这个方向以正值或负值进行测量。
现在你理解了这个概念,你可以找到任何点相对于原点的坐标。坐标是一对数值——每个轴一个——表示从原点的距离。原点的坐标是 (0, 0)。例如,给定坐标 (3, 4),你可以通过在 x 轴上移动 3 像素,在 y 轴上移动 4 像素来找到这个点(记住,每个轴上的第一条线是 0,不计算在内)。同样,坐标 (−3, −1) 表示一个点在 x 轴上远离原点 3 像素,且在 y 轴上远离原点 1 像素。你可以在图 6-2 的图表上看到这两个值。

图 6-2:两组坐标
如果这些听起来非常复杂,不用担心——你已经在使用笛卡尔坐标系了,比如background-position这样的属性;只是你没有意识到而已。
在 CSS 中,所有元素都有高度和宽度,每个都表示为一个像素的数值(即使使用其他长度单位如 em 或百分比)。高度和宽度一起创建了一个像素网格;例如,一个大小为 10px × 10px 的元素,其像素网格为 100px。如果你认为元素的原点位于左上角,那么像background-position这样的属性的两个位置值就完全对应于 x 和 y 坐标。
注意
在 CSS 中,默认的原点是元素的左上角,但这并不是固定不变的;某些 CSS 属性允许你更改原点的位置。例如,你可以将原点设置在元素的正中心、右下角或任何你希望的位置。我们将在本书后面看到这一点,例如在第十二章处理 2D 转换时。
应用维度效果:text-shadow
使用 text-shadow 属性为文本添加阴影的功能已经存在很长时间了;Safari 首次在 1.1 版本中实现了该功能,并在 2005 年发布。所以你可能会好奇,为什么我在一本关于 CSS3 的书中讨论它。和第五章中的字体属性一样,text-shadow 在 CSS2.1 中由于缺乏实现而被删除,但这个属性已经在 CSS3 规范中重新加入,并且如今在浏览器中得到了良好的实现。
阴影的位置是通过我刚才介绍的 x 和 y 坐标来设置的。最简单的语法形式接受两个值:x 用于设置文本的水平距离(称为 x-offset),y 用于设置垂直距离(即 y-offset):
E { text-shadow: x y; }
默认情况下,阴影将继承自父元素的颜色(通常是黑色),因此如果你想指定不同的颜色,你需要为其提供一个值,例如:
E { text-shadow: x y color; }
这是一个示例,展示了一个灰色(十六进制代码 #BBB)阴影,位于原始文本的右侧 3px 和下方 3px:
h1 { text-shadow: 3px 3px #BBB; }
你可以在图 6-3 中看到此代码的输出。

图 6-3:简单的*text-shadow*
偏移值不仅限于正整数;你还可以使用 0(零)和负数来获得不同的效果。以下是一些示例:
➊ .one { text-shadow: -3px -3px #BBB; }
➋ .two { text-shadow: -5px 3px #BBB; }
➌ .three { text-shadow: -5px 0 #BBB; }
你可以在图 6-4 中看到这些示例的输出。

图 6-4:*text-shadow* 的不同轴偏移值
第一个示例(➊)使用了两个轴的负值,因此阴影渲染在文本的上方和左侧。下一个示例(➋)对 x 轴使用负值,对 y 轴使用正值,因此阴影渲染在文本的下方和左侧。最后一个示例(➌)对 x 轴使用负值,对 y 轴使用 0 值,因此阴影渲染在文本的左侧,并且位于同一基线。
text-shadow 属性还具有第四个选项:*blur-radius*。此选项设置阴影模糊效果的范围,必须在偏移值之后使用:
E { text-shadow: x y blur-radius color; }
模糊半径值与两个偏移值一样,也是带有长度单位的整数;值越大,模糊效果越宽(且越浅)。如果没有提供值(如图 6-4 中所示的示例),则默认模糊半径为 0。以下是几个示例:
.one { text-shadow: 3px 3px 3px #BBB; }
.two { text-shadow: 0 0 3px #000; }
你可以在图 6-5 中看到这些示例的输出。

图 6-5:*text-shadow* 的不同模糊值
在第一个示例中,我设置了与图 6-4 中相同的偏移值,但模糊半径为 3px。结果是一个更柔和、更“自然”的阴影。在第二个示例中,我设置了偏移值为 0,模糊半径为 3px,使文本与背景匹配,产生文本被提升的错觉。
多个阴影
你不必局限于单一阴影——text-shadow 的语法支持为文本节点添加多个阴影。只需为属性提供额外的值,并使用逗号分隔它们,如下所示:
E { text-shadow: value, value, value; }
阴影按照你提供的值顺序应用。图 6-6 展示了多个阴影效果的两个示例。

图 6-6:使用多个值的*text-shadow*
这些示例的 CSS 如下所示。第一个示例有一个类 one,第二个示例有一个类 two。请注意,我已将它们缩进以便于理解。
.one {
text-shadow:
0 -2px 3px #FFF,
0 -4px 3px #AAA,
0 -6px 6px #666,
0 -8px 9px #000;
}
.two {
color: #FFF;
text-shadow:
0 2px rgba(0,0,0,0.4),
0 4px rgba(0,0,0,0.4),
0 6px rgba(0,0,0,0.4),
0 8px 0 rgba(0,0,0,0.4);
}
在第一个示例中,我将 x 偏移保持为 0,同时将 y 偏移的负值从 −2px 增加到 −8px。模糊半径从 3px 增加到 9px,颜色逐渐变暗,形成文本后面幽灵般的淡轮廓,随着远离文本,阴影变得更暗。
在第二个示例中,x 偏移量仍然保持一致,但这次 y 偏移量的值增加了。由于没有指定 *blur-radius* 值,它保持为 0。在这里,我使用了 rgba() 颜色函数(在 第十章 中有解释),所以颜色保持不变但部分透明,创建了重叠效果。尽管值的变化相对较小,但这两个元素之间的视觉差异非常显著。
如我之前所说,text-shadow 在浏览器中实现得很好,IE10+ 和所有其他主流浏览器,包括移动端,都支持该功能。
限制溢出
在某些情况下——例如在屏幕空间有限的移动设备上——你可能希望将文本限制为单行且固定宽度,尤其是在展示链接列表时,这样你不希望链接文本换行。如果文本超出其容器并被中断,可能会非常令人沮丧。
在 CSS3 中,为了应对这种情况,新增了一个名为 text-overflow 的属性。它的语法如下:
E { text-overflow: keyword; }
允许的关键字值是 clip 和 ellipsis。默认值是 clip,它的作用如我之前所描述的:文本在溢出容器元素的地方被截断。但新的值——ellipsis——非常有趣,它会在溢出之前的最后一个完整或部分字符处用一个省略号字符(…)代替。
让我们通过以下 CSS 示例来演示:
p {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
在这个 p 元素中,我将 overflow 的值设置为 hidden,以防止内容显示在边框外,white-space 属性的值设置为 nowrap,以防止文本换行,并且将 text-overflow 属性的值设置为 ellipsis。你可以在 图 6-7 中查看与默认行为相比的效果。

图 6-7:带有 *text-overflow* 属性值为 *ellipsis* 的效果(底部)
句子中的最后一个词被截断,并且用省略号代替了被移除的字符,表示该行已经被截断。
text-overflow 属性最初是在文本模块中定义的,但后来被移到了基本用户界面模块中(* www.w3.org/TR/css3-ui/*)。该属性已在所有主流浏览器中实现。
该规范还以两种方式扩展了基本语法。首先,你可以为该属性提供两个值——一个是行尾的溢出值,另一个是行首的溢出值。例如,如果你给文本设置了负缩进并且文本超出了容器,你可以在两端加上省略号:
E { text-overflow: ellipsis ellipsis; }
第二个扩展提供了一个第三个可能的关键字值,这个值是一个字符串,可以替代省略号,就像这个示例中我用波浪号来展示溢出效果:
E { text-overflow: '~'; }
这些扩展目前仅在 Firefox 中实现,并在规范中标记为可能在以后被移除。
文本对齐
text-align属性已经存在很长时间,但 CSS3 为它新增了两个值:start和end。对于从左到右阅读的人来说,它们分别等同于left和right。然而,它们的真正用途是在国际化网站中,特别是那些可能使用从右到左的文本的站点。你可以在大多数现代浏览器中使用这些新值,但 Internet Explorer 除外。
CSS3 新增了text-align-last属性,允许你设置对齐方式,以便对齐文本块中的最后一行(或唯一一行)文本。此属性接受与text-align相同的值:
E { text-align-last: keyword; }
所以,如果你想让一块文本对齐,但也希望最后一行对齐到右边,你可以使用:
p {
text-align: justify;
text-align-last: right;
}
截至写作时,这个扩展已在 Internet Explorer 中实现(不过,仍没有start和end关键字值),在 Firefox 中以-moz-前缀实现,并且在 Chrome 中也有实现。
控制行换行
在处理动态文本时,常常遇到的一个问题是行在不合适的位置换行。例如,当你提供一个事件的详细信息时,你希望开始时间和结束时间显示在同一行,但如果出现动态换行,结束时间可能会被推到下一行。CSS3 通过一对属性,让你能更清晰地定义如何让内容换行,从而让你对这些问题有更多的控制权。
拆分单词
第一个属性是word-wrap,它指定浏览器是否可以拆分长单词以使其适应父元素。它的语法非常简单:
E { word-wrap: keyword; }
此属性允许使用normal或break-word的关键字值。前者只允许在单词之间换行(除非在标记中另有指定),后者则允许单词在必要时被拆分,以防止父元素溢出。
所以,举个例子,如果我想允许长单词换行,而不是让它们溢出其包含元素,我可以使用:
p.break { word-wrap: break-word; }
图 6-8 展示了这个效果。左边的块没有使用单词换行,而右边的块则使用了。

图 6-8:带有(左)和不带(右)*break-word*值的*word-wrap*的文本示例
word-wrap属性在所有主流浏览器中得到广泛支持,包括 Internet Explorer。最近的文本模块版本已将word-wrap重命名为overflow-wrap,不过一些浏览器——例如 Chrome 和 Safari——已经实现了新的名称,尽管出于兼容性原因,旧名称也将继续得到支持。
单词连字符
如果你更喜欢有一个额外的选项来将单词分隔到多行,可以使用连字符。连字符在印刷中长期作为标准,指示单词断开的位置。你可以在 HTML 中使用软连字符符号实体 ­ 来对文本进行连字符——尽管这要求你标记所有内容,这在某些情况下并不可行。
CSS3 通过 hyphens 属性使这一点变得稍微简单一些:
E { hyphens: keyword; }
hyphens 有三个可能的关键字值:manual 仅在标记中存在连字符建议时进行单词断开——也就是使用上一段中提到的软连字符符号;auto 即使没有连字符建议,也会在适当的位置断开单词;而 none 永远不会断开单词,即使存在建议。
警告
自动连字符仅在指定文档语言且浏览器能够访问该语言的连字符词典时才会发生。
你可以在 图 6-9 中看到连字符的例子。左侧的段落没有应用连字符,而右侧的段落则应用了 auto 值;单词“conversations”(高亮显示)已被浏览器进行连字符,并分断到两行。

图 6-9:右侧的段落启用了自动连字符
hyphens 属性目前的实现比较零散:它在 IE10+、Firefox 和 Safari 中都有实现,并且每个浏览器都有相应的厂商前缀,但最近已从 Chrome 中移除(因为 Chrome 本来就不支持自动连字符)。它也出现在 iOS 的 Safari 中。
调整元素大小
另一个对于内容宽度大于其容器的元素非常有用的新属性是 resize 属性。这个属性允许你通过提供一个控制柄来控制元素的尺寸,使你可以拖动元素调整到不同的大小。
该属性的语法如下:
E { resize: keyword; }
关键字值指定了元素可以被拖动的方向:horizontal(水平)、vertical(垂直)、both(两者)或 none(无)。在以下示例中,我将展示一个 p 元素,resize 属性的值为 both,使用以下代码:
p {
overflow: hidden;
resize: both;
}
图 6-10 展示了在 Mac 上 Firefox 中如何显示可调整大小的元素。

图 6-10:一个可调整大小的文本框在右下角有一个条纹状的控制柄
resize 属性在 Chrome、Firefox 和 Safari 中得到支持——尽管在移动浏览器中实现可能不太稳定,因为它们的输入控制较为粗糙。还需注意,调整元素大小可能无法通过键盘进行,因此不要把调整大小设为必须的操作。
总结
过去几年,网页排版质量有了显著提升,尽管有限的 CSS 文本属性使得这一点并不容易。但我相信浏览器厂商已经注意到对更好实现的需求,并且,尽管缓慢,但排版控制正逐步掌握在我们手中。
在第五章中,我探讨了如何增加字体的种类和范围,而在本章中,我讨论了如何让这些字体更具装饰性、灵活性,并且——最重要的是——易于阅读。第七章将通过介绍一种全新的文本布局方式,完成关于字体和排版的三章内容。嗯,对网页来说是新的;不过印刷商已经使用这种方式好几个世纪了。
文本效果和排版样式:浏览器支持情况
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
text-shadow |
支持 | 支持 | 支持 | IE10 |
text-overflow |
支持 | 支持 | 支持 | 支持 |
text-align (新值) |
支持 | 支持 | 支持 | 不支持 |
text-align-last |
支持 | 支持^(*) | 不支持 | 支持^(†) |
overflow-wrap |
支持 | 支持^(‡) | 支持 | 支持^‡ |
hyphens |
不支持 | 支持* | 支持* | IE10* |
resize |
支持^(§) | 支持 | 支持^§ | 不支持 |
- 带有厂商前缀
† 没有起始和结束值
‡ 类似于 word-wrap
§ 不支持移动浏览器
第七章:7
多列

尽管近年来桌面和笔记本电脑屏幕变得更宽,但研究仍然表明人们在阅读长行文本时感到困难。(通常认为每行大约 65 到 75 个字符是比较舒适的阅读长度。)这种惯例导致了限制性的布局和未能充分利用更宽屏幕所带来的机会的网站。
多年来,杂志和报纸一直使用多列布局来流式化内容——解决了长文本行的问题,以及如何在有限的空间内容纳大量文案的问题。现在,随着 CSS3 中多列布局模块的出现(* www.w3.org/TR/css3-multicol/*),网站也能利用多列布局了。
多列布局模块目前已达到候选推荐状态,这意味着该模块被认为基本完成,并且在 IE10+ 和其他现代浏览器中得到了良好的实现(尽管有一些小的限制),因此你有充足的机会来尝试使用多列布局。
列布局方法
你可以使用两种方法将内容分成列:一种是预设列数,另一种是动态设置列宽,让浏览器自动计算适合父元素宽度的列数。
请注意,尽管在撰写本文时,Chrome、Firefox、Safari 和 IE10+ 都支持本章中的属性,但后者是唯一一个不需要厂商前缀即可实现的浏览器。不同的浏览器在实现上也有一些小差异,我会在接下来的内容中指出这些问题。
预设列:column-count
将内容均匀分配到多个列中的最简单方法是使用 column-count 属性:
E { column-count: columns; }
元素 *E* 是你想要分列的内容的父元素,*columns* 值是一个整数,用来设置列数。例如,要将内容流式化到 div 元素的两列中,你可以使用:
div { column-count: 2; }
让我们来看一个实际的例子。我将演示几段文本,第一次展示为两列,第二次展示为三列。这里是我将使用的代码:
div[class*='-2'] { column-count: 2; }
div[class*='-3'] { column-count: 3; }
注意
在这些例子中,我使用了任意子字符串属性值选择器, 这是我在第三章中介绍的。
你可以在图 7-1 中看到此代码的效果。

图 7-1:文本分成两列,再分成三列
这是我为示例 图 7-1 所使用的标记(已编辑以简化内容):
<div class="columns-2">
<p>A young man…</p>
</div>
<div class="columns-3">
<p>A young man…</p>
</div>
语法非常简单,浏览器会负责均匀分配内容。
动态列:column-width
划分内容为列的第二种方法可能更适合灵活的布局。它不是指定列数,而是使用column-width属性指定每列的宽度,浏览器会根据父元素的宽度填充尽可能多的列。语法也非常简单:
E { column-width: length; }
与column-count一样,*E*是你想要划分为列的内容的父元素。但column-width的不同之处在于,它需要一个*length*值:可以是一个长度单位(例如 px 或 em)或一个百分比。以下是一个例子:
div { column-width: 150px; }
这段代码将div的子元素分成宽度为 150px 的列,并沿div的宽度重复这些列。我们来看一下它是如何工作的,使用以下样式规则:
.columns {
column-width: 150px;
width: 710px;
}
这里,我有一个名为columns的元素,宽度为 710px;其中的内容将被分布到宽度为 150px 的列中。你可以在图 7-2 中看到它的渲染效果。
将column-width设置为 150px 时,浏览器已创建四列来填充父元素。但情况并非看起来的那样。记住,父元素的宽度是 710px,即使每列之间有 12px 的间隙(稍后会讲到),总宽度也只有 636px,那多出来的空白空间去哪了呢?

图 7-2:文本跨越动态创建的、间距相等的列
创建列的算法其实非常智能,它会自动调整列的大小,使它们更好地适应父元素。它使用 150px 作为最小值,直到每列的宽度足够,直到总宽度与父元素相匹配——在这个例子中,每列的宽度被调整为 168.5px。
内容在列中的不同分布
默认情况下,流入多列的内容会尽可能均衡地分布在列中,以使每列的长度相同。如果浏览器无法均匀安排内容,最后一列会被缩短。这可能比解释更容易理解,因此图 7-3 展示了使用默认布局方法将文本分布在三列上的效果。

图 7-3:文本动态分布在三列上
你可以看到所有三列的行数相同。如果你想更改这个默认行为,可以使用column-fill属性来实现:
E { column-fill: keyword; }
这个属性有两个可能的关键字值:默认值是balance,它试图使所有列的长度相等,如图 7-3 所示;另一种选择是auto,它按顺序填充列。
auto值仅在父元素具有固定高度时生效。内容首先流入第一列以填充高度,然后流入下一列,直到该列填满,以此类推。
警告
当使用*auto*值时,可能看起来你有比*column-count*属性指定的列数少,但实际情况并非如此;只是有些列会是空的。
你可以在图 7-4 中看到column-fill的auto值示例;前两列有相等数量的行,第三列少了三行,因为文本仅仅流入列中,浏览器并没有尝试平衡它们。

图 7-4:使用*auto*值将内容流入列中,作为*column-fill*属性的值
目前,只有 Firefox 和 IE10+浏览器实现了这个属性,尽管 Chrome 和 Safari 在父元素设置固定高度时,自动表现得像应用了auto值一样。
结合 column-count 和 column-width
你可以在一个元素上同时设置column-count和column-width属性,尽管一开始你可能认为这样做会产生冲突。然而,这种可能性已经被考虑在内:如果两个属性都应用于同一元素,column-count的值作为最大值。为了说明这一点,让我们参考图 7-2,但将 CSS 更改为同时包括column-count属性:
.columns {
column-count: 3;
column-width: 150px;
}
这是其背后的逻辑:将文本分成每列 150px,除非这样会创建三列或更多列,在这种情况下,创建三列,最小宽度为 150px。
如果你回顾一下图 7-2 中显示的示例,你会记得,考虑到父元素的宽度为 710px,column-width属性生成了四列。然而,当你在同一元素上应用这两个属性时,column-count属性优先,结果只会分配三列,它们的宽度会动态调整以最好地适应父元素。你可以在图 7-5 中看到结果。

图 7-5:当结合列属性时, *column-count* 作为最大值。
如果你希望一起使用这两个属性,提供了一个简写属性:
E { columns: column-width column-count; }
所以,如果你使用图 7-5 中的值与这个简写属性,它看起来会是这样:
div { columns: 150px 3; }
列间距和规则
在使用规定的多列布局时,浏览器应在每列之间设置默认的 1em 间距。然而,你可以通过使用两个新属性:column-gap 和 column-rule,来更改默认值并指定自己的间距。
第一个属性,column-gap,设置列与列之间的间距,它的语法很简单:
E { column-gap: length; }
*length*值是任何带有标准 CSS 长度单位的数字。以下是一个示例,在生成的每列之间设置 2em 的间距(不过不会在第一列或最后一列的外侧添加间距):
div { column-gap: 2em; }
第二个属性,column-rule,画一条线,类似于边框,等距离地分隔列。column-rule的语法实际上是三个子属性的简写:column-rule-width、column-rule-style和column-rule-color。这些子属性的值与它们在 CSS2 中的等效border-*属性完全相同。以下是语法:
E {
column-rule-width: length;
column-rule-style: border-style;
column-rule-color: color;
column-rule: length border-style color;
}
如果你添加了实际值,结果会是这样的:
div {
column-rule-color: silver;
column-rule-style: double;
column-rule-width: 0.3em;
}
然后,你可以使用column-rule简写一次性设置所有三个子属性:
div { column-rule: 0.3em double silver; }
让我们看看column-gap和column-rule属性的实际效果。以下示例将它们与column-count结合使用:
.columns {
column-count: 3;
column-gap: 2em;
column-rule: 0.3em double silver;
}
这个示例将元素的子元素分成三列,每列之间有 2em 的间隙,并且有一个 0.3em 的分隔线。分隔线的厚度对布局没有影响——无论你把分隔线做得多厚,间隙的宽度始终保持不变。你可以在图 7-6 中看到这个示例的结果。

图 7-6:列间间隙和规则
列内包含元素
到目前为止,在这些示例中我只使用了文本块,这些文本块整齐地流入列中。但对于像图像这样可能宽于所包含列的较大元素,会发生什么呢?让我们看看当我在列布局中添加一个比单列更宽的图像时会发生什么,使用img元素。结果显示在图 7-7 中。

图 7-7:一个比列宽的*img*元素^(1)
如你所见,图像显示在第二列中,但溢出部分在column-gap的中间位置被剪切。到目前为止,唯一与此行为不同的浏览器是 Firefox,它(不幸的是)保持图像的宽度,但将后续列的内容流过它,如图 7-8 所示。

图 7-8:Firefox 在显示宽元素时与其他所有浏览器不同。
希望这个问题将在未来的版本中修复(也许在你读到这篇文档时就已经修复了)。
为了完全解决这个问题,我可以将图像的max-width属性设置为100%,因为宽度是根据包含列的宽度计算的,而不是父元素的宽度。
跨越多列的元素
有时,你可能希望某些元素跨越多个列——例如,一个子标题,用来分隔故事的各个部分。为了处理这些元素,该模块引入了column-span元素。以下是语法:
E { column-span: value; }
在这种情况下,*value*只能是两种可能值之一:all 或 none。默认值是 none,这将使元素保持在列流中。另一种可能的值是 all,这会打断列流——元素之前的所有内容会分配到各列中,元素之后的所有内容也会分配到各列中,但元素本身——被称为 跨越元素——则不会。
在以下示例中,h2 元素的 column-span 值为 all,因此它跨越了多个列,导致文本流断开。你可以在图 7-9 中看到效果。
h2 { column-span: all; }

图 7-9:一个 *h2* 元素跨越了两列
到目前为止,Firefox 是唯一不支持此属性的浏览器。
总结
尽管 CSS3 让你的内容流入列中变得很容易,但它所带来的挑战不在于技术性问题,而在于实际应用问题:如果你想使用比列宽更宽的图片,或者你想在狭窄的列中使用长标题,怎么办?
尽管在某些情况下使用多列布局肯定是合适的,但在使用之前请三思是否适合你的内容。确保在使用这些属性之前,你完全掌控了你的内容,并且如果你的客户没有专门且认真负责的网络内容团队,最好不要设计依赖多列布局的网站。
同时,请记住,屏幕分辨率可能会有很大差异,对你而言看起来可读的内容可能对网站访客来说并不可读。如果他们必须频繁地上下滚动,这可能会引起很多困惑,甚至可能导致他们完全不再访问你的网站。不过,话虽如此,巧妙地使用多列布局可以让你的内容更具可读性。
在最后三章中,我描述了 CSS3 如何提供格式化和呈现文本副本的方法,帮助你更好地控制排版和布局。接下来,我将介绍如何改善网站的整体视觉效果,首先从新的背景和边框效果开始。
多列布局:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
column-count |
是^(*) | 是* | 是* | IE10 |
column-width |
是* | 是* | 是* | IE10 |
columns |
是* | 是* | 是* | IE10 |
column-fill |
否 | 是* | 否 | IE10 |
column-gap |
是* | 是* | 是* | IE10 |
column-rule |
是* | 是* | 是* | IE10 |
column-span |
是* | 否 | 是* | IE10 |
- 使用厂商前缀
第八章:8
背景图片

为了让我们的网站更具视觉吸引力,往网页添加装饰元素曾经是一个非常消耗资源和时间的过程。即便是看似简单的图形效果,比如在同一个元素上使用两个背景图片,也需要大量不必要的标记,这反过来使得页面渲染变慢,维护也变得更加困难。
CSS3 引入了许多新的和扩展的属性,旨在使元素装饰变得更加简单,浏览器厂商也迅速实现了这些特性,并且加入了许多自家的实现。在接下来的几章中,我们将一一探讨新的功能特性,从背景图片到装饰性边框,再到新的颜色效果。
我将首先带你走一遍背景与边框模块(* www.w3.org/TR/css3-background/*)。由于网页开发者的需求很大,它所带来的新属性已经得到了浏览器的广泛实现。Internet Explorer 9 完全实现了本章列出的属性和变化,其他主流现代浏览器也都实现了这些特性,因此,除非文中另有说明,你可以假设这些属性得到广泛支持。
由于背景与边框模块相当庞大,我将其分为两章进行讲解,首先从背景图片属性开始。背景图片已经是 CSS 的一部分多年,但与之前的版本不同,在 CSS3 中,你可以对元素应用多个图片,并且可以动态调整这些图片的大小。仅这两个新特性就足以让我们大多数人感到高兴,但该规范更进一步,提供了对图片位置和平铺方式的更多控制。
现有背景属性的更新
许多其他的 CSS3 模块引入了新的属性,甚至是全新的概念,但背景与边框模块的强大之处在于它扩展了现有的属性,使其更强大且更有用。虽然这个模块并非没有创新—它当然有,而且我很快就会介绍—但其中的细微变化才是亮点,在这一节中,我将讲解你在 CSS2.1 中熟悉的属性的扩展和变动。
background-position
在 CSS2.1 中,background-position 属性接受两个值:每个盒子边缘的关键字(如 top、right 等),或者设置相对于应用该属性的元素左上角的长度或百分比值。虽然这对于许多任务来说已经足够,但当我们进行页面布局时,它并没有提供我们所期望的精细控制。
在 CSS3 中,该属性现在最多接受四个值:你可以使用关键字指定一侧,然后使用长度或百分比值来表示相对该侧的距离。来看一下这个示例代码:
.foo { background-position: right 10em bottom 50%; }
元素*.foo*上的背景图像将位于从右边 10em 的位置和从底部 50%的位置。这种定位在 CSS2.1 中非常困难;你必须知道所有涉及元素的宽度,并且这些宽度不能变化。
background-attachment
背景图像在视口中滚动的方式由background-attachment属性决定。CSS2.1 中允许的值有scroll(默认值),意味着图像不会与应用该属性的元素一起滚动,而是与视口一起滚动;fixed,意味着图像既不与元素滚动,也不与视口滚动。
在 CSS3 中引入了一个新的值local;这个值允许图像同时与其元素和视口一起滚动。这在静态书籍中几乎无法演示,所以我建议你查看书籍附带网站上的示例文件 8-a(thebookofcss3.com/)。
新的值在 IE9+及所有其他主要现代桌面浏览器中得到支持。然而,移动浏览器往往使用不同的视口布局机制,在这些机制中,固定元素并不真正起作用,因此你可能会在这些浏览器中遇到意外的(或根本没有)行为。
background-repeat
在 CSS2.1 中,background-repeat属性接受四个可能的值之一:no-repeat、repeat、repeat-x和repeat-y。使用这些值,你可以在元素上水平或垂直(或两者)平铺图像,但它们并不提供比这更细致的控制。然而,CSS3 通过两种方式扩展了这个属性的功能:一对新的属性和语法上的一个调整。
新的属性之一是space,它设置背景图像在其包含元素中尽可能多地重复,而不剪裁图像。所有重复的图像(除了第一个和最后一个)之间的间隔是均等的,因此图像是均匀分布的。
第二个是round,它同样将背景图像设置为尽可能多地重复而不剪裁,但与均匀间隔重复不同,图像会缩放,以便完整的图像数填充包含元素。
为了比较这两者之间的区别,我准备了一个示例,其中将不同的background-repeat值应用于两个元素,使用以下代码:
.space { background-repeat: space; }
.round { background-repeat: round; }
图 8-1 展示了结果。左侧的元素作为参考,具有默认的background-repeat值repeat,并展示了你当前期望的行为。中间的元素的值为space,显示出在不剪裁或缩放的情况下,可以重复的最大图像数量,并且它们之间有空隙。最后,右侧的元素的值为round,它计算出可以在水平和垂直方向上适应包含元素的最大整数,并根据需要缩放图像。
目前,只有 Internet Explorer 9+ 和 Chrome 正确实现了这些关键词。Safari 识别它们,但会使它们表现不正确,就像应用了 no-repeat 一样。Firefox 会忽略它们,并使用之前的级联或继承值。

*图 8-1:*background-repeat* *的值:*repeat* *(左边),*space* (中间),和 *round* (右边)^(1)
我还提到了一些语法的变化。现在,你可以分别控制两个轴上的平铺,因为该属性现在接受两个值。第一个值控制水平轴上的平铺,第二个值控制垂直轴上的平铺。所以,如果你希望背景图像在垂直方向上重复并带有圆角,而在水平方向上具有间距,可以使用以下代码:
.foo { background-repeat: round space; }
结果如图 8-2 所示。

图 8-2:应用于水平和垂直方向的不同 *background-repeat* 值
多个背景图像
背景和边框模块中的第一个新特性不是新属性,而是对现有属性的扩展——或者说,是对多个现有属性的扩展。使用 CSS2.1 时,你只能为元素应用单一的背景图像,但在 CSS3 中,(几乎所有的)background-* 属性现在接受多个值,因此你可以为一个元素添加多个背景图像。
要做到这一点,你只需要将各个值用逗号分隔。例如,这是使用 background-image 的语法:
E { background-image: value, value; }
对于你创建的每个背景图层,你可以为所有相关的 background-* 属性添加适当的值。以下是一个实际的例子:
h2 {
background-image: url('monkey.svg'), url('landscape.jpg');
background-position: 95% 85%, 50% 50%;
background-repeat: no-repeat; }
你可以在图 8-3 中看到它的效果。图层是按相反的顺序创建的——也就是说,列表中的第一个图层变成最上面的图层,依此类推。在我的示例代码中,monkey.svg 是位于 landscape.jpg 上方的图层。background-position 属性遵循相同的顺序:风景图像位于其容器元素的水平和垂直中心,即 50% 左和 50% 上,而猴子图像位于 95% 左和 85% 上。

图 8-3:同一元素上的两个背景图像^(2)
请注意,我只给了 background-repeat 一个值;如果一个属性的值比背景图层少,那么这些值会重复。在这个例子中,这意味着 no-repeat 会应用于所有背景图层。
你可以使用 background 简写属性来设置多个值;与单独的属性一样,你只需要提供一个用逗号分隔的值列表。为了得到与图 8-3 中相同的效果,我也可以使用以下代码:
h2 {
background:
url('monkey.svg') no-repeat 95% 85%,
url('landscape.jpg') no-repeat 50% 50%;
}
我在本节开始时提到,几乎所有的背景属性都可以有多个值。然而,background-color是个例外,因为颜色层始终会堆叠在所有其他背景层下方。如果你想在使用简写属性时指定背景颜色,必须将其放在逗号分隔列表中的最后一个实例中。以我的示例代码为例,它将位于包含风景图片的那个实例中:
h2 {
background:
url('monkey.svg') no-repeat 95% 85%,
url('landscape.jpg') no-repeat 50% 50% #000;
}
动态缩放背景图片
CSS3 中的新属性是background-size。顾名思义,这个属性允许你设置背景图像的大小。其语法如下:
E { background-size: value; }
该属性的值可以是两个长度或百分比的组合,一个长度或百分比,或者一个关键字。如果使用一对值,语法如下:
E { background-size: width height; }
要将背景图像调整为宽 100px、高 200px,可以使用:
div { background-size: 100px 200px; }
长度可以是任何标准的测量单位。如果使用百分比,尺寸是基于包含元素的,而不是背景图像。因此,100%的宽度和高度,例如,将会把背景图像拉伸以填满容器。要使图像按自然尺寸显示,请使用auto关键字。
如果只指定一个值,该值将被视为宽度,且高度将被赋予默认值auto。因此,以下这两个示例是完全等效的:
div { background-size: 100px auto; }
div { background-size: 100px; }
你可以使用你新学到的多重背景方法与background-size一起使用。例如,让我们重新查看图 8-3,但重复猴子图像几次,并为background-position和background-size属性添加不同的值。下面是代码:
h2 {
background:
url('monkey.svg') no-repeat 95% 85%,
url('monkey.svg') no-repeat 50% 80%,
url('monkey.svg') no-repeat 10% 100%,
url('landscape.jpg') no-repeat 50% 50%;
background-size: auto 80%, auto 15%, auto 50%, auto;
}
图 8-4 展示了这种方法的应用。第一只猴子的background-size是 80%,第二只猴子是 15%,最后一只猴子是 50%;在所有情况下,水平大小都被设置为auto,以保持图像的比例。

图 8-4:多重缩放背景图像的示例
除了长度值之外,还可以使用两个关键字:contain和cover。contain关键字使图像按比例缩放到尽可能大,但不超过包含元素的高度或宽度;cover使图像缩放到包含元素的高度或宽度,取较大的值。
看一下以下代码,看看我的意思:
.monkey-1, .monkey-2 {
background-image: url('monkey.svg');
background-position: 50% 50%;
}
.monkey-1 { background-size: contain; }
.monkey-2 { background-size: cover; }
我使用了两个元素,分别设置了类monkey-1和monkey-2,并为每个元素的background-size设置了不同的关键字值。结果如图 8-5 所示。

图 8-5: *background-size* 关键字: *contain* (左) 和 *cover* (右)
左侧的盒子使用了contain关键字值,因此背景图片垂直填充盒子(最短的长度);右侧的盒子使用了cover关键字值,因此背景图片水平填充盒子(最长的长度),并在顶部和底部被裁剪。
背景裁剪与起始点
在 CSS2 中,背景图像的位置是相对于其包含元素的外部填充边界来定义的,任何溢出都会延伸到边框下方。CSS3 引入了两个新的属性,可以更细致地控制这种位置。
第一个属性是background-clip,它设置了盒子模型的某个部分,成为背景(无论是颜色还是图像)显示的限制区域。下面是语法:
E { background-clip: box; }
*box*值可以是三个关键字之一:border-box、content-box或padding-box。border-box是默认值,背景显示在边框后面(如果使用透明或半透明边框颜色,你可以看到它)。padding-box值使背景仅显示到边框前,不能显示在边框后。content-box意味着背景仅在元素的填充区域内显示。
我将使用以下代码来说明不同的区别:
h2 {
background: url('landscape.jpg') no-repeat 50% 50% #EFEFEF;
border-width: 20px;
padding: 20px;
}
h2.brdr { background-clip: border-box; }
h2.pddng { background-clip: padding-box; }
h2.cntnt { background-clip: content-box; }
我使用了三个h2元素,分别具有brdr、pdding和cntnt类。图 8-6 展示了不同值之间的差异。

图 8-6:展示不同值对*background-clip*属性的影响:*border-box*(左)、*padding-box*(中)、和*content-box*(右)
我使用了半透明的边框(我将在第十章中解释如何做到这一点),因此你可以在左侧的盒子中看到背景图像涂抹在边框下方,该盒子使用的是border-box值。中间的盒子使用的是padding-box值,正如你所见,背景在填充的边界处停止。在右侧的盒子中,值为content-box,因此背景不会显示在填充区域后面。
第二个可以让你更细致控制的属性是background-origin。使用background-origin,你可以设置背景开始计算的位置。正如我之前提到的,CSS2 中的背景位置是相对于填充的边界计算的,但background-origin允许你改变这一点。下面是语法:
E { background-origin: box; }
*box*值接受与background-clip中相同的关键字:border-box、content-box和padding-box。我将使用以下代码解释不同的结果:
h2 { background: url('monkey.svg') no-repeat 0 100%;}
h2.brdr { background-origin: border-box; }
h2.cntnt { background-origin: content-box; }
h2.pddng { background-origin: padding-box; }
不同值的效果如图 8-7 所示。如你所见,由于background-position是相对于每个盒子的不同点计算的,猴子在每个盒子中的位置不同(我已添加背景网格,使其更容易查看)。
background-position 总是设置为 0 100%,即左下角。然而,左下角的测量点会根据 background-origin 的值而变化。在第一个框中,背景从边框的极限开始;在第二个框中,从填充区域的极限开始;在第三个框中,从内容框的极限开始。

图 8-7: *background-origin* 属性,具有 *border-box* (左), *padding-box* (中),和 *content-box* (右)的值
需要记住的几点:首先,如果 background-position 设置为 fixed,此属性将无效。其次,background-clip 和 background-origin 都接受多个值,采用与“多重背景图像”中相同的语法,该内容位于 第 88 页。
更新的背景快捷方式
background 快捷属性已更新,包含 background-size、background-clip 和 background-origin 属性的值。background-size 的值应紧跟在 background-position 后,并用斜杠分隔,如下所示:
E { background: url('bar.png') no-repeat 50% 50% / 50% auto; }
在这种情况下,背景图像,bar.png,将被定位在元素的正中央,宽度设置为元素的 50%,高度自动调整。
对于 background-clip 和 background-origin,如果只提供一个框值(border-box、padding-box 或 content-box),则这两个属性都将设置为该值。如果提供两个框值,第一个将设置为 background-origin,第二个将设置为 background-clip。举个例子,看看这个简写代码:
E { background: url('bar.png') no-repeat padding-box content-box; }
在这种情况下,背景图像的原点将是填充框,图像将被裁剪到内容框。
总结
本章介绍的新功能是实现 CSS 既定目标的一大步:将页面的内容与展示分离。背景图像的更多灵活性意味着创建我们想要的效果所需的元素更少,而我们从文档中移除的非必要标记越多,页面的维护就越轻松,语义性也会更好。
本章只介绍了背景和边框模块的一半内容,所以下一章我将介绍剩下的一部分——你可以从章节标题“边框和框效果”中猜到,这部分内容与边框有关。
背景图像:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
background-position (边缘值) |
是 | 是 | 是 | 是 |
background-attachment |
是 | 是 | 是 | IE10 |
background-repeat (新值) |
是 | 否 | 否^(*) | 是 |
background-repeat (两个值) |
是 | 是 | 是 | 是 |
| 多重背景图像 | 是 | 是 | 是 | 是 |
background-size |
是 | 是 | 是 | 是 |
更新的 background 属性 |
是 | 是 | 是 | 是 |
background-clip |
是 | 是 | 是 | 是 |
background-origin |
是 | 是 | 是 | 是 |
- 这些值被识别,但未正确显示。
第九章:9
边框和盒子效果

自 CSS1 时代以来,向页面元素添加边框的功能几乎没有变化。然而,开发者想要在边框上做的事,早在多年前就超出了他们能够做到的范围。添加像圆角或阴影这样的边框效果,可能已经导致了比几乎任何其他东西更多不必要的空白标记元素。这些本应简单的事情,往往需要一些非常复杂的解决方案。
我们对背景和边框模块的第二部分探索了不需要额外标记的新方法来装饰元素。你将学习如何制作圆角,使用图片作为边框,以及添加阴影。
给元素的边框添加圆角
自互联网诞生以来,设计师们一直在为页面元素添加圆角。没有办法不使用图片就创建圆角,简直是不可思议。要创建一个具有四个圆角的灵活宽度框,需要制作四张图片并添加至少两个额外的非语义元素,这使得维护一个网站变得比必要的要困难得多。
但现在不需要再这样做了。背景和边框模块引入了一种仅使用 CSS 就能给元素的角落添加圆角的方法。每个角落都被视为四分之一椭圆,其曲线是通过* x 轴上的一个点和 y *轴上的一个点之间画出来的(你可能记得这些来自第六章)。图 9-1 更清楚地说明了这一点。
四分之一椭圆可以是规则的,即两个轴的长度相同;也可以是不规则的,即每个轴上的长度不同。图 9-2 展示了两者的例子。

图 9-1:由 x-轴和 y-轴之间的曲线形成的四分之一椭圆

图 9-2:一个规则的曲线(左)在两个轴上的值相同;一个不规则的曲线在每个轴上的值不同。
CSS3 使用 border-radius 属性定义这些曲线。使用此属性,你可以通过以下语法简单地定义四分之一椭圆的半径:
E { border-v-h-radius: x y; }
在这个语法中,*v* 是 top 或 bottom 的关键字值;*h* 是 left 或 right 的关键字值;*x* 和 *y* 值是定义四分之一椭圆曲线的轴向长度。这听起来有点复杂,但下面的示例应该能让你更清楚:
div { border-top-right-radius: 20px 20px; }
这个语法会将 div 元素的右上角圆角半径设置为 20px,水平和垂直方向上都是规则曲线。
实际上,对于规则的曲线,border-radius 让你可以进一步简化,省略 *x* 或 *y* 的值;如果没有指定一个值,默认两个值相等。所以,如果你想将这个半径应用到元素的每个角落,你可以使用以下代码:
div {
border-top-left-radius: 20px;
border-top-right-radius: 20px;
border-bottom-right-radius: 20px;
border-bottom-left-radius: 20px; }
要创建具有不规则圆角的形状,你只需对各个属性使用不同的值:
div {
border-top-left-radius: 10px 20px;
border-top-right-radius: 10px 20px;
border-bottom-right-radius: 10px 20px;
border-bottom-left-radius: 10px 20px;
}
你可以在图 9-3 中比较这两个不同的代码示例:左边的形状使用第一个代码片段,具有四个常规圆角,而右边则是第二个代码片段的结果,具有四个(相等的)不规则圆角。

图 9-3:两个元素,一个具有常规圆角(左),一个具有不规则圆角(右)
border-radius 简写
如果每个角都写不同的属性让你觉得很重复,你会很高兴地发现有一种简写属性可用。像border-width、margin和padding一样,你可以指定一个、两个、三个或四个值。不过,虽然这些值在边缘上有特定的指代,但border-radius的值是指角,顺序从左上角开始:
E { border-radius: [top-left] [top-right] [bottom-right] [bottom-left]; }
E { border-radius: [top-left] [top-right & bottom-left] [bottom-right]; }
E { border-radius: [top-left & bottom-right] [top-right & bottom-left]; }
E { border-radius: [top-left & top-right & bottom-right & bottom-left]; }
所以,如果我想对一个div的左上角和右上角应用 20px 的值,对右下角和左下角应用 10px 的值,以下是我使用的代码:
div { border-radius: 20px 20px 10px 10px; }
注意
使用这种简写语法只会创建常规的圆角;稍后我会介绍不规则圆角的简写方式。
为了演示简写属性的实际应用,我将对三个元素应用规则,每次使用不同的一组值:
.radius-1 { border-radius: 0 20px; }
.radius-2 { border-radius: 0 10px 20px; }
.radius-3 { border-radius: 0 0 20px 20px; }
你可以在图 9-4 中查看结果。第一个(左)框有两个border-radius值:左上角和右下角的值为 0,因此是方形的,而右上角和左下角则有 20px 的圆角。第二个(中)框有三个值:左上角依然是方形的,但现在右上角和左下角有 10px 的圆角,而右下角的半径为 20px。最后,第三个(右)框有四个值:左上角和右上角的值为 0,因此是方形的,而右下角和左下角有 20px 的圆角。

图 9-4:不同值对 *border-radius* 简写属性的影响
你还可以使用简写语法来处理不规则的曲线。为了实现这一效果,你需要用斜杠(/)分隔不同的值:
border-radius: { horizontal-radius / vertical-radius; }
每个斜杠的两侧可以包含一个到四个值,就像常规圆角的简写一样。这意味着,举例来说,你可以为水平半径指定一个值,并为垂直半径指定四个不同的值。接下来,我将通过一些示例来展示这是什么样子:
.radius-1 { border-radius: 20px / 10px; }
.radius-2 { border-radius: 20px / 10px 20px; }
.radius-3 { border-radius: 10px 20px 20px / 20px 10px; }
图 9-5 显示了结果。第一个(左)框有四个相等的角,水平半径为 20px,垂直半径为 10px。第二个(中)框有两个角的半径为 20px/10px,另外两个角的半径为 20px/20px。第三个(右)框的左上角为 10px/20px,右上角和左下角为 20px/10px,右下角为 20px/20px。

图 9-5:通过*border-radius*简写属性生成的不规则圆角
使用百分比值
本章中的示例使用了长度单位,但你也可以使用百分比值来定义border-radius,即它应用于元素一边的长度百分比。如果你想在 CSS 中制作一个完美的圆形,这将特别有用:一个具有四个相等曲率的正方形元素,每个曲率都是边长的一半,能创造一个完美的圆形元素。
下一个示例展示了两个元素,它们在每个角上都应用了相同的百分比borderradius值(50%)。这两个元素的高度相同,但一个的宽度是另一个的两倍:
div {
border-radius: 50%;
height: 100px;
}
.ellipse { width: 200px; }
.circle { width: 100px; }
结果如图 9-6 所示。左边的元素长度较宽,因此角落的圆角形成了一个椭圆。右边的元素具有相等的高度和宽度,结果是一个完美的球形。

图 9-6:在*border-radius*上使用百分比值生成椭圆(左)和圆形(右)。
使用图像作为边框
另一种常见的元素样式化方法是使用背景图像作为装饰性边框。然而,在 CSS2 中,你无法实现这一点,你不得不使用大量额外的标记来实现期望的效果,导致语义和可维护性方面的损失。CSS3 引入了一系列属性,提供了一种简洁的语法来应用装饰性边框。
border-image-source
第一个属性border-image-source设置将用于边框的图像来源——不过你可能已经猜到了。它接受一个值,该值是图像数据类型;对于大多数浏览器,这个值通常是url()函数。以下是border-image-source的一个示例:
E { border-image-source: url('foo.png'); }
注意
该属性还应该接受渐变函数(参见第十一章)作为值,但目前只有 Chrome 和 Firefox 支持这种用法。
border-image-slice
一旦你确定了用于边框的图像来源,你就需要对其进行切片。border-image-slice属性接受一个到四个值,每个值对应元素的一个边,类似于margin、padding、border-radius等。这些值用于设置从图像每个边缘到“框住”元素的距离。
我知道这可能有点让人困惑,所以我会用一个示例来解释。看看这段代码:
E { border-image-slice: 34; }
注意,这里没有使用单位值。这个数字有两个用途:对于位图图像(如 JPG 或 PNG),单位是像素值;但对于矢量图像(如 SVG),它们是坐标值。你也可以使用百分比值作为替代。
在我的示例代码中,我只提供了一个单一的值,设置了我想要切割的区域:顶部、右侧、底部和左侧各 34px。请看图 9-7,它展示了这个值如何将源图像分成九个部分:四个角落(c1、c2 等),四个边(称为切片—slice1、slice2 等),以及中央的填充部分。每个切片将被放置到目标元素的边框上,位于相应的位置。
定义了源图像和切片之后,现在我只需要给元素设置一个边框来应用边框图像。图像将应用于边框所创建的区域,因此在下面的代码示例中,我定义了上下边框为 34px,左右边框为 10px。
E {
border: 34px 10px;
border-image-slice: 34;
border-image-source: url('foo.png');
}

图 9-7:在*border-image*中指定的值将如何切割图像
你可以在图 9-8 中看到这段代码是如何应用的:上下的图像切片与上下边框的高度相同,因此图像按其自然高度应用,而左右切片则应用于宽度不到其一半的边框,因此图像会被压缩以适应。角落的切片则会扭曲以适应两个不同的尺寸。

图 9-8:这里,34px 的图像切片应用于 34px 宽、10px 高的边框。
边框图像的默认行为是仅使用边框上的切片和角落,将元素的中心留空,显示其背景属性。border-image-slice属性有一个可选的关键字值fill,如果包含fill关键字,图像切片内的区域将应用于元素背景的适当位置。
E { border-image-slice: value fill; }
图 9-9 展示了一个带有边框图像的元素的比较,分别是带有和不带有fill关键字的情况。
注意
使用*border-image-slice*填充元素意味着你无法控制填充的大小,可能会导致其被严重扭曲,因此在选择使用这种方式而不是*background-image*时,需要仔细考虑。

图 9-9:从左到右:源图像,作为边框图像应用,及带有*fill*关键字值的效果
border-image-width
正如你在图 9-8 中看到的,边框图像的切片会根据元素边框的宽度进行拉伸或压缩;例如,如果元素的边框宽度为 10px,但你的切片宽度为 40px,每个切片将被压缩到其高度或宽度的四分之一,以适应。你可以通过使用border-image-width属性来控制这一点:
E { border-image-width: value; }
像border-width或border-image-slice一样,这里的值实际上可以有最多四个值,以匹配元素的四个边,每个值可以是长度、百分比或无单位的数字。
*value*创建了一个“虚拟”边框,意思是它对页面布局或流动没有影响;与border-width不同,*value*创建的边框仅仅是视觉效果,对盒子模型没有影响。为了说明区别,请看这段代码:
E { border-width: 34px; }
F {
border-width: 1px;
border-image-width: 34px; }
E, F {
border-image-slice: 34;
border-image-source: url('foo.png'); }
你可以在图 9-10 中看到这个比较:左侧的元素每边有 34px 的边框,并且没有显式的border-image-width值,因此文本内容从边框内侧开始显示;然而右侧的元素只有 1px 的边框,但其border-image-width值为 34px。尽管图像片段的应用方式相同,右侧的文本内容却位于“虚拟”边框的顶部。

图 9-10:比较*border-width*值为 34px(左)与*border-image-width*值为 34px(右)的情况
如果你使用一个无单位的数字,它会作为现有border-width值的乘数;在以下代码中,border-image-width将等于 20px:
E {
border-width: 10px;
border-image-width: 2;
}
border-image-outset
默认情况下,边框图像从边框盒子的外侧开始显示,逐渐向内容盒子内部移动;但是你可以通过外扩图像,让它从边框盒子的外部开始。你可以使用border-image-outset属性来改变这种默认行为,它接受(现在通常使用的)四个可能的长度值,每个值对应边的一个方向。例如,要将边框图像从上下各外扩 10px,从左右各外扩 5px,你可以使用以下规则:
E { border-image-outset: 15px 30px; }
你可以在图 9-11 中看到这个比较;左侧的元素使用了默认的border-image-outset值 0,而右侧的元素使用了代码示例中显示的值;尽管它们的盒子大小相同,右侧的边框图像明显外扩,甚至覆盖了左侧的元素。

图 9-11:右侧的元素有一个外扩的边框图像。
border-image-repeat
另一个与边框图像相关的属性是:border-image-repeat。此属性控制图像如何适配每个边框的长度:
E { border-image-repeat: keyword; }
它接受三种关键字值中的一个:stretch(默认值)、repeat和round。本章迄今为止所有的示例插图都使用了默认值(stretch),即图像片段被拉伸以填满边框的长度。使用repeat时,图像片段按其自然长度进行应用,并重复直到填满所应用的边框的长度;因此,如果图像片段不能正好适配边框的长度,可能会被切掉。最后一个值round,表现得像repeat,只不过它会根据需要将图像片段缩放,以最适合边框的长度,而不会被切掉。
这比说起来更容易理解,所以你可以在图 9-12 中看到这三个值的不同。这三个元素应用了所有border-image属性相同的值,除了border-image-repeat。对于这个属性,第一个元素使用默认值stretch,第二个元素使用repeat,最后一个元素使用round。

图 9-12:不同的 *border-image-repeat* 值:(从左到右) *stretch*,*repeat*,*round*
我在前几段中提到过,border-image-repeat接受三种关键字值之一,但那并不完全准确;我只是为了让后续的解释更清晰才这么说的。希望你能原谅我误导了你。事实上,你可以在这个属性上使用三个关键字值中的两个;第一个控制水平重复,第二个控制垂直重复。所以,如果你希望在上下边框上拉伸元素,并在左右边框上使其圆角,你可以使用如下规则:
E { border-image-repeat: stretch round; }
border-image 简写属性
为了节省时间和减少键入,你可以使用border-image简写来设置前面描述的所有属性。语法如下所示:
E { border-image: source slice / width / outset repeat; }
以下代码示例展示了所有单独的属性应用于一个元素的效果,然后使用简写属性将相同的属性应用于另一个元素:
E {
border-image-source: url('foo.png');
border-image-slice: 25 10 fill;
border-image-width: 25px 10px;
border-image-outset: 5px;
border-image-repeat: round; }
F { border-image: url('foo.png') 25 10 fill / 25px 10px / 5px round; }
这样可以节省不少空间。
浏览器支持
本节中的所有border-image属性在 Chrome、Firefox、Safari 6+和 Internet Explorer 11+中都得到支持。一些较老的浏览器——特别是移动版 Safari 5.1 及以下版本,以及 Android 4.3 及以下版本的默认浏览器——支持边框图像,但仅支持使用border-image简写,并且在这种情况下,border-image-width和border-image-outset属性不被支持。
投影阴影
在第六章中,我们探讨了使用text-shadow属性为文本添加投影阴影的方法,但 CSS3 也有一种为盒子元素添加阴影的方法。你可以使用box-shadow属性;其语法与text-shadow类似:
E { box-shadow: inset horizontal vertical blur-radius spread color; }
第一个值,inset,是一个可选的关键字,用于设置阴影是在元素内部还是外部。我会在下一节中更详细地解释这个概念;目前你只需要知道,如果没有指定inset,阴影将位于元素的外部。接下来的两个值,与text-shadow一样,是设置阴影相对于盒子在*水平*和*垂直*方向上的距离;如果你希望有阴影,这些值是必需的。
下一个值设置*模糊半径*,它是另一个长度值,同样,和text-shadow的作用完全相同。接下来是*扩展*,它是另一个长度值,用于设置阴影的扩展距离。正的长度使阴影比元素本身更大,而负的长度则使阴影更小。*模糊半径*和*扩展*都是可选的。
最后,你有*color*值,这也是可选的,如果未指定,则默认继承颜色(通常为黑色)。
现在,我将通过一个示例将这些内容结合起来。接下来的代码片段创建了一个深灰色的阴影,位于元素的外部,水平和垂直距离为 4px,模糊半径为 3px:
div { box-shadow: 4px 4px 3px #666; }
在下一个代码片段中,我将演示不同值对box-shadow属性的进一步影响。效果显示在图 9-13 中。
.shadow-one { box-shadow: 4px 4px; }
.shadow-two { box-shadow: 4px 4px 3px; }
.shadow-three { box-shadow: 12px 12px 2px -6px; }
.shadow-four { box-shadow: #999 4px -4px 2px 0; }
.shadow-five { box-shadow:
#999 4px -4px 2px 0,
-4px 4px 2px;
}

图 9-13:使用不同的*box-shadow*属性值的效果
代码中的元素对应于图 9-13 中的框,按从左到右的顺序排列。第一个是最简单的阴影,距离元素水平和垂直 4px,使用继承的颜色。第二个与第一个的距离值相同,但还添加了 3px 的模糊半径,以柔化阴影的边缘。第三个的水平和垂直距离为 12px,但具有负的扩展值(-6px),使得阴影比框体小。第四个示例具有中等灰色的阴影,并且垂直距离为负值,这意味着阴影出现在元素的上方,而不是下方。
第五个框应用了两个阴影,每组值用逗号分隔。第一组值与第四个框相同,第二组值创建了一个黑色(或继承的颜色)阴影,水平距离为负值,导致阴影出现在框的左侧。
内凹阴影
我在前一节开始时简要提到了可选的inset关键字。如果存在该关键字,它会在框的内部绘制阴影,但它也会产生“翻转”阴影到框的另一侧的效果。我的意思是,常规的—即 外部—阴影,具有正的 x 和 y 值时,会出现在框的右下角,而内凹阴影则会出现在框的左上角。
为了说明这一点,我将使用与之前示例相同的代码,但在每个例子中都添加inset关键字:
.shadow-one { box-shadow: inset 4px 4px; }
.shadow-two { box-shadow: inset 4px 4px 3px; }
.shadow-three { box-shadow: inset 12px 12px 2px -6px; }
.shadow-four { box-shadow: inset #999 4px -4px 2px 0; }
.shadow-five { box-shadow:
inset #999 4px -4px 2px 0,
inset -4px 4px 2px;
}
结果显示在图 9-14 中,正如你所看到的,它几乎是图 9-13 的反向效果;所有的偏移、模糊半径和颜色值都相同,但阴影现在出现在框的内部,并且位于相反的角落。

图 9-14:内凹阴影
box-shadow属性得到了广泛实现,在 IE9+ 及所有其他现代浏览器中无需前缀支持。旧版本的 iOS Safari 和 Android 默认浏览器需要使用-webkit-前缀。
总结
我在前一章的开头提到,背景和边框模块之所以成为 W3C 的优先事项,是因为网页开发者的强烈呼声。它引入的新属性对于移除仅用于视觉样式的容器元素非常有用,这些容器元素通常在标记中多余,给开发者提供了更精细的页面呈现控制。随着我们可以使用许多新的背景和边框属性,创建能够在多种尺寸和分辨率下查看的网站将变得更加容易,我们的网站也将更加符合跨平台的理想。
在下一章中,我将继续讲解页面装饰主题,重点介绍 CSS3 提供的新颜色和透明度控制。
边框和盒子效果:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
border-radius |
是 | 是 | 是 | 是 |
border-image |
是 | 是 | 是 | IE11 |
box-shadow |
是 | 是 | 是 | 是 |
第十章:10
颜色与不透明度

在 CSS2 中,颜色基于红色、绿色、蓝色(RGB)模型;无论你使用的是十六进制值还是rgb()函数值,都需要将这三种颜色混合在一起为页面添加颜色。当然,设计师通常使用色调和色泽的术语:当设计师说要使用某种颜色的“50%色泽”时,开发人员必须使用 RGB 模型来匹配该颜色,这通常涉及到使用图形软件来精确找到所需的色调。
CSS 色彩模块 (www.w3.org/TR/css3-color/) 提供了解决这一问题的方法——还有更多的解决方案。首先,它通过opacity属性和Alpha颜色通道引入了不透明度的概念。此外,CSS 色彩模块还添加了一个全新的颜色模型,这个模型更加直观,也更容易调整以找到完美的色调。
色彩模块是 W3C 的推荐标准,并在 IE9 及以上版本以及其他主流浏览器中得到了很好的实现,因此,通过一些小心的编码为旧版 IE 提供备用方案,你可以立即开始使用其属性和值。
不透明度属性
严格来说,不透明度是衡量物体对光的阻力——物体越不透明,它透过的光就越少。没有不透明度的物体是完全透明的。
在 CSS 中,不透明度是通过opacity属性来测量的。实际上,使用opacity时,你设置的是指定元素中可以透过背景看到多少内容。opacity属性的语法如下:
E { opacity: number; }
*number*值是一个十进制小数——即一个介于 0.0 和 1.0 之间的数字,0.0 表示完全透明,1.0 表示完全不透明,介于这两者之间的任何值则表示不透明度和透明度的混合。例如,要将一个元素设置为 50%不透明(或者说 50%透明,取决于你的玻璃是半空还是半满),你可以使用以下规则:
E { opacity: 0.5; }
为了进一步演示,我将展示一组元素——一个父div,里面有一个子div,而该子div里又有一个子p——重复三次:
<div class="parent">
<div class="child">
<p>…</p>
</div>
</div>
我将对每组元素应用相同的规则,唯一的变化是opacity值的不同。以下是本示例的相关规则:
.parent { background-color: black; }
.child { background-color: white; }
.child.semi-opaque-1 { opacity: 0.66; }
.child.semi-opaque-2 { opacity: 0.33; }
你可以在图 10-1 中查看输出。第一个(左侧).child元素没有显式设置opacity值,因此默认值为 1.0,即完全不透明——它的背景是白色。接下来(中间)的.child设置了0.66,因此其不透明度减少了三分之一,导致白色背景看起来像浅灰色(这是父元素的黑色背景色和元素本身的白色背景色的混合,透过了元素的透明部分)。最后一个(右侧)的.child设置了opacity值为0.33,因此可以认为它是三分之二透明的,使得盒子的颜色变得更暗。

图 10-1:不同*opacity*值对三个相同元素的影响
现在,这里有一点非常重要的内容需要记住:不透明度不仅会影响应用该属性的元素,还会影响该元素的所有子元素。如果我为一个元素设置了opacity值为 0.5,那么它的子元素的透明度永远不能比它更不透明。这个设置无法通过其他属性覆盖——换句话说,你永远无法让子元素比父元素更不透明,但你可以让它更透明。
这可能会让opacity属性看起来有些有限,因为你不能单独应用它于有子元素的元素而不影响它们的子元素。然而,CSS3 提供了一种新的方法来解决这个限制,它叫做 Alpha 通道,我将在下一节中进行解释。
新的和扩展的颜色值
CSS2.1 允许通过三种方法指定颜色值:关键字(black)、十六进制表示法(#000000)和 RGB(0,0,0)。在 CSS3 中,新增了一种完全不同的颜色指定方法(参见“色相、饱和度、亮度”在第 116 页),并通过 Alpha 通道引入了不透明度。
Alpha 通道
Alpha 通道(简称Alpha)是颜色的不透明度度量——与opacity属性不同,后者是元素的不透明度度量。所以,尽管使用 Alpha 的颜色值可以像任何其他颜色值一样被子元素继承,元素的整体不透明度不会受到影响。
CSS3 将 Alpha 作为RGBA颜色模型中的一个值引入。RGBA 代表红色、绿色、蓝色、Alpha,并通过rgba()函数应用。语法与 CSS2 中使用的rgb()函数值相同,但 Alpha 值是通过末尾的额外逗号分隔的参数指定的:
E { color: rgba(red, green, blue, alpha); }
*alpha*参数的值与opacity提供的值相同:一个从 0.0 到 1.0 的十进制分数,再次表示从完全透明(0.0)到完全不透明(1.0)之间的度量。如果你想让元素的前景色为黑色,且不透明度为 50%,你可以使用以下代码:
E { color: rgba(0,0,0,0.5); }
如前所述,rgba()与opacity属性有两个不同之处:首先,rgba()是一个颜色值,因此你不能例如用它来改变图像(或带有背景图像的元素)的透明度。其次,尽管rgba()函数的值可以被继承,子元素可以通过自己的rgba()值覆盖它。
为了更精确地说明这两者之间的区别,我将使用以下标记创建两个相同的代码块:
<div class="box">
<div class="text">
<h1>…</h1>
</div>
</div>
我将对这个标记应用以下 CSS 规则:这两个元素将会获得相同的规则,唯一的区别是我会为其中一个设置opacity,并为另一个的background-color设置rgba()值,两个值的十进制分数都是0.5:
.box { background-image: url('monkey.svg'); }
.text { background-color: white; }
.opacity { opacity: 0.5; }
.rgba { background-color: rgba(255,255,255,0.5); }
结果显示在图 10-2 中,差异非常明显。两个框的透明度相同,但在第一个框中,opacity 值已经被子元素 p 继承,这也使得文本变得半透明。在第二个框中,rgba() 值严格应用于 .text 元素的 background-color,因此 p 元素保持完全不透明的黑色 color。

图 10-2:比较透明度(左)和 RGBA(右)
确定 rgba() 不等于 opacity 后,让我们来看看它是如何工作的。作为一种颜色值,它显然可以用于背景、边框、阴影等。以下代码展示了 rgba 应用于不同属性的示例:
➊ .shadow .text { box-shadow: 10px 10px 4px rgba(0,0,0,0.7); }
➋ .border .text { border: 10px solid rgba(0,0,0,0.5); }
➌ .text-semi p { color: rgba(0,0,0,0.6); }
➍ .text-shadow p { text-shadow: 5px 5px 1px rgba(0,0,0,0.6); }
图 10-3 展示了这些属性的实际效果。从左上角顺时针移动,在第一个框中,rgba 降低了 box-shadow 的不透明度;将 Alpha 值设置为 0.7 ➊ 使得部分背景透过阴影显示出来,使阴影看起来更加“真实”。下一个示例展示了一个 50% 不透明的黑色 border ➋(这是我在图 8-6 中使用的示例,见第 93 页)。接下来的示例中,color 属性的 Alpha 值被设置为 0.6 ➌,这使得文本看起来半透明。最后一个示例展示了另一个阴影效果,这次应用于 text-shadow 属性。Alpha 值设置为 0.6 ➍,同样使得阴影更加真实。

图 10-3:RGBA 应用于不同属性
RGBA 和优雅降级
不支持 RGBA 值的旧版浏览器(尤其是 IE8)会忽略使用这些值的规则,并回退到之前指定或继承的值。为了解决这个问题,你应该将颜色指定两次——首先指定没有 Alpha 值的颜色,然后指定带有 Alpha 值的颜色——利用层叠规则确保正确的颜色被实现:
p {
color: #F00;
color: rgba(255,0,0,0.75);
}
在这个例子中,不支持 RGBA 值的浏览器会忽略第二个 color 属性,并应用第一个 color 属性。当然,这意味着会使用一个完全不透明的颜色,而不是半透明的颜色,因此要彻底检查你的设计,确保它不会受到负面影响。
其余章节中引入的所有新颜色值也适用相同的原则。
色相、饱和度、亮度
我在本章早些时候提到过,CSS3 引入了一种新的颜色表示法系统,这种系统被称为 HSL。准确总结 HSL 是什么而不提供一门颜色理论课程是很困难的,但我会尽力解释:HSL——代表色相(Hue)、饱和度(Saturation)、亮度(Lightness, 有时称为 明度)——是一种颜色空间的圆柱坐标表示法。如果还是不清楚的话,可以看看图 10-4。

图 10-4:HSL 颜色表示法 ^(1)
所有可能的颜色都被排列在一个有中心轴的圆柱体中。轴周围的角度表示色相;从轴的距离表示饱和度;沿轴的距离表示亮度。这三者的组合创造了独特的颜色。
色相代表主要颜色,从红色(0 或 360)开始和结束,并包括之间的所有主要颜色。可以将你在学校学习过的可见光谱的颜色(或彩虹的颜色)想象成排列在圆周上——红色、橙色、黄色、绿色、蓝色、靛蓝色和紫色——色相值就是指向特定颜色的圆周上的角度。
饱和度是颜色的强度或浓度:0%代表没有强度,使颜色呈灰色,而 100%是完全强度,即该颜色最强烈的版本。
亮度是颜色的明暗程度:50%是原色,0%是黑色,100%是白色。
如果你以前没有接触过 HSL,而我的解释仍然让你感到困惑,不要担心——对于我们的用途,你只需要理解 HSL 是通过hsl()颜色值函数应用的。它接受三个参数,语法类似于rgb():
E { color: hsl(hue,saturation,lightness); }
*色相*值是一个介于 0 和 360 之间的数字(代表色轮上的角度),而*饱和度*和*亮度*则接受从 0%到 100%之间的值。以下是一些简单的颜色示例及其十六进制和 RGB 等效值,见表 10-1。
表 10-1:四种颜色值的常见等效色
| HSL | RGB | 十六进制 | 关键词 |
|---|---|---|---|
| 0,0%,0% | 0,0,0 | #000000 | 黑色 |
| 360,0%,100% | 255,255,255 | #FFFFFF | 白色 |
| 0,100%,50% | 255,0,0 | #FF0000 | 红色 |
| 120,100%,25% | 0,128,0 | #008000 | 绿色 |
| 240,100%,50% | 0,0,255 | #0000FF | 蓝色 |
注意
即使你使用的是饱和度和亮度值为 0(零)的情况,你仍然需要将该值指定为百分比。
表 10-1 实际上并没有讲述完整的故事——例如,许多不同的值组合可以让你创造纯白色。实际上,了解更多关于 HSL 的最佳方式是使用一个支持 HSL 的颜色选择器,并玩一玩。Photoshop 和大多数其他图形软件都有 HSL 颜色选择器,你也应该能在线找到颜色转换工具。然而,注意不要将 HSL 与色相、饱和度、明度(HSV)混淆——虽然它们使用相同的语法,但颜色值的映射方式不同,因此你不会得到相同的结果。
HSL 相比 RGB(或十六进制)的优势在于,它允许你更快速地尝试不同的颜色变体,例如让某种颜色变浅/变暗或更/更少强烈。HSL 的灵活性使它对网页设计师更为有用。然而,如果你是从他人的设计中进行开发,你可能仍然希望继续使用 RGB。HSL 仅仅是一个可以考虑的新选项。
HSLA
如果你已经决定 HSL 是适合你的颜色方法,那么你也可以使用 hsla() 颜色值函数来利用 Alpha 通道实现透明度。和它的对应函数 rgba() 一样,hsla() 只是通过在函数中添加一个额外的参数来扩展颜色方案:
E { color: hsl(hue,saturation,lightness,alpha); }
所以,例如,如果你想要一个颜色值为红色并且具有 50% 不透明度的元素,你可以使用以下规则:
E { color: hsl(0,100%,50%,0.5); }
颜色变量:currentColor
除了我刚才描述的新的颜色方法外,CSS3 还引入了一个新的 color 值关键字:currentColor。这个关键字作为当前颜色的变量:一个元素的 currentColor 值就是它自己的 color 属性的值。所以,当一个元素的 color 值是例如 red 时,它的 currentColor 值也是 red。你可以利用这一点,在不同的属性上设置颜色值,而无需再次指定 red。
以下示例应该能阐明 currentColor 的有用性。首先,我使用以下的标记:
<h2>The Central Intelligence Agency (<abbr>CIA</abbr>). </h2>
<h2 class="ccolor">The Federal Bureau of Investigation (<abbr>FBI</abbr>)</h2>
然后我为它应用这个 CSS:
h2 { color: black; }
.ccolor {
background-color: black;
color: white;
}
h2 abbr { border-bottom: 6px double currentColor; }
一个 h2 元素在默认(white)背景上显示黑色(black)文本,另一个则在黑色(black)背景上显示白色(white)文本。接下来,我使用 currentColor 关键字作为 abbr 元素的 border-bottom 属性的值。你可以在图 10-5 中看到结果。

图 10-5:*currentColor* 颜色值关键字的演示
因为第一个 h2 元素的 color 值是 black,所以 abbr 元素的 border-bottom 属性颜色也是 black。因为第二个 h2 元素的 color 值是 white,所以 abbr 元素的 border-bottom 属性也具有相同的颜色。这些值已经继承了它们父元素的 color 属性。
currentColor 关键字意味着我不需要为每个 abbr 元素实例指定边框颜色。在这个示例中,我不需要使用额外的规则——如果没有它,我必须像这样使用:
h2 abbr { border-bottom: 6px double black; }
.ccolor abbr { border-bottom-color: white; }
虽然这看起来可能不算是一个大节省,但 currentColor 的引入意味着我可以更新父元素的颜色,而不必担心为任何相关的子元素设置颜色。在一个拥有许多不同颜色组合的大型网站上,你可以看到 currentColor 将会非常方便。
概述
opacity 属性和 Alpha 通道的引入对 CSS3 来说可能看似微不足道,但透明度为页面设计带来了显著的变化;重叠的颜色长期以来是印刷设计的常见元素,但由于实现难度大,这种风格从未真正进入网页设计领域。
尽管 HSL 色彩模型并不会影响访客浏览网站的体验,但它使得你作为作者,在设计过程中能更轻松地进行色彩实验。
在下一章中,我将通过探讨渐变背景,完成关于元素装饰的四部曲。
颜色与透明度:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
opacity |
是 | 是 | 是 | 是 |
| RGBA 值 | 是 | 是 | 是 | 是 |
| HSL 值 | 是 | 是 | 是 | 是 |
| HSLA 值 | 是 | 是 | 是 | 是 |
currentColor 值 |
是 | 是 | 是 | 是 |
第十一章:11
渐变

在本章中,作为关于装饰属性四部曲的最后一章,我将介绍 CSS 渐变。在这个上下文中,渐变是指两种或更多颜色之间的逐渐过渡——这是多年来的设计标准,虽然你可能认为它在 CSS 中实现会比较简单,但它在 Web 上的历史却长且曲折。
CSS 渐变首次在 2008 年由 WebKit 引入,并出现在 Safari 4 中。然而,它们使用的语法与本章其余部分展示的完全不同,并且被其他浏览器供应商认为过于复杂。接下来几年中提出了多种其他建议(并且甚至得到了实现),直到 2011 年底最终达成了一种语法。这种最终的语法被所有浏览器迅速采纳,并且就是我将在本章中介绍的语法。(请阅读 “浏览器支持与遗留语法” 在 第 131 页了解如何在遗留浏览器中支持旧版本。)
警告 图形效果如渐变可能会对计算资源产生较大负担,尤其在移动浏览器中,会降低页面的渲染速度和性能。这并不是说你应该避免使用渐变,而是要在构建网站时,始终在性能和外观之间进行权衡。
线性渐变
线性渐变是指在连接两点的直线的长度上,颜色逐渐过渡的渐变。在最简单的情况下,线性渐变在整条线的长度上按比例地从两种颜色之间过渡。
我将从展示线性渐变的最简语法开始,线性渐变通过在background-image属性中使用linear-gradient()值函数来定义:
E { background-image: linear-gradient(black, white); }
图 11-1 显示了结果。

图 11-1:一个简单的上下两色线性渐变
每个你希望渐变经过的颜色被称为color-stop,并以逗号分隔的参数列表形式传递给函数。如你所见,渐变至少需要两个 color-stop:一个开始色和一个结束色。在这个例子中,渐变从黑色开始,到白色结束,逐渐经过两个值之间的所有中间色调。
设置渐变方向
第一个和最后一个 color-stop 之间的轴线被称为渐变线。在前面的例子中,渐变线从盒子的顶部开始,垂直向下延伸。这是默认的方向。要设置不同的渐变线,可以在颜色停止列表之前,通过传递一个新的参数来指定盒子的目标边或角。这个参数是一个包含关键词的字符串,关键词以to开头,后跟一个或两个方向关键词。例如,要定义一个从下到上的黑白渐变,可以使用以下值:
E { background-image: linear-gradient(to top, black, white); }
要将相同的渐变改为从左上角到右下角对角线的方向,你需要使用两个方向关键词:
E { background-image: linear-gradient(to right bottom, black, white); }
若要更精细地控制渐变线的方向,可以使用角度参数代替方向关键词。角度可以使用多种单位进行声明——在本章中,我将坚持使用度数(deg),因为它是最常见的单位,但有关其他单位的更多信息,请参见第 124 页的“角度单位”部分。
角度值设置渐变线的角度:0deg(或360deg)从下到上,45deg从左下到右上,90deg从左到右,依此类推。负值使渐变逆时针方向:-45deg与315deg相同,-315deg与45deg相同,依此类推。你明白了。
例如,要创建与前一个代码示例中相同的从左上到右下的渐变,但使用角度值,你可以使用以下代码:
E { background-image: linear-gradient(135deg, black, white); }
下一个代码片段展示了三个不同方向值的效果示例:第一个从右到左,第二个从左下到右上,最后一个是 120 度的角度(大致,虽然不完全是,从左上到右下)。
E { background-image: linear-gradient(to left, black, white); }
E { background-image: linear-gradient(to top right, black, white); }
E { background-image: linear-gradient(120deg, black, white); }
图 11-2 展示了结果。

图 11-2:三个不同的方向值:从左到右,从左下到右上,以及 120 度
添加额外的颜色停止值
到目前为止,我使用的是一个简单的渐变,只有两个颜色停止值,但你可以使用更多的颜色停止值。(因为本书是黑白印刷的,所以我只能选择有限的色板!)每个添加的颜色都通过在逗号分隔的列表中添加一个新的颜色停止值来声明,正如这个示例中,我添加了第三个黑色停止值:
E { background-image: linear-gradient(black, white, black); }
角度单位
一种用于表示角度测量的角度单位在 CSS3 值和单位模块中定义(www.w3.org/TR/css3-values/#angle/)。我们大多数人在学校学到的单位是度数(deg),其中一整圈有 360 度,这也是我在本章中使用的单位。其他单位之一是梯度(grad),其测量范围是以圆周一圈为 400 梯度为标准。另一个是弧度(rad),其基于 π(pi),一整圈是 2π 弧度,约为 6.2832rad。还有一个单位是turn,表示一整圈等于 1 圈,尽管截至本文写作时,只有 Firefox 和 Internet Explorer 9+ 支持此单位。
表 11-1 显示了不同角度单位的一些等效值。
表 11-1: 角度值的等效单位
| 度数 | 梯度 | 弧度 | 圈数 |
|---|---|---|---|
| 60 | 66.66667 | 1.04720 | 0.16667 |
| 90 | 100 | 1.5708 | 0.25 |
| 240 | 266.66667 | 4.18879 | 0.66667 |
如你所见,度数通常提供最简洁且最熟悉的值,因此,除非你在进行某些非常特定的数学计算,如微积分,否则通常会使用度数。
颜色停顿点会按照列出的顺序处理,因此这个示例创建了一个从黑色到白色再回到黑色的渐变。图 11-3 展示了结果。

图 11-3:一个包含三个颜色停顿点的渐变
颜色停顿点沿渐变的长度均匀分布,因此,在这种情况下,白色的颜色停顿点正好位于两个黑色停顿点之间,即渐变的中间位置。你可以通过在每个颜色停顿点后添加长度或百分比值,来调整颜色停顿点在渐变线上的位置。例如,以下代码将白色颜色停顿点放置在渐变线的 75% 位置:
E { background-image: linear-gradient(black, white 75%, black); }
图 11-4 展示了结果。与图 11-3 中未定义位置的情况相比,你会看到白色的颜色停顿点已经沿渐变线向元素底部进一步移动。

图 11-4:第二个颜色停顿点出现在渐变长度的四分之三(或 75%)处。
你不仅可以在颜色停顿点上使用百分比值,也可以使用长度值。接下来的代码片段展示了三种使用长度和百分比值的方法,结果见图 11-5。我将在代码之后逐一讲解。
➊ div { background-image: linear-gradient(to right, black, white 75%); }
➋ div { background-image: linear-gradient(to right, black 50%, white); }
➌ div { background-image: linear-gradient(to right, black, white 50%, black 1px); }

图 11-5:在颜色停顿点中使用不同的定位值
在➊中,最后一个颜色停顿点的参数位置为 75%,因此该颜色停顿点从该位置开始,并继续以纯色直到结束。在➋中,第一个颜色停顿点具有位置值,因此继承的颜色(黑色)会显示为一个固态块,直到渐变线的 50%位置,届时渐变将过渡到最终的颜色停顿点值。
最后,➌包含三个颜色停顿点。第二个颜色停顿点从 50%位置开始,因此第一个和第二个颜色停顿点会过渡到该位置。最后的颜色停顿点则仅比前一个停顿点沿渐变线多出一个像素,因此该颜色会突然切换(没有过渡),并继续到达渐变的终点。
重复线性渐变
与其仅创建一个从元素一侧到另一侧的渐变,你还可以使用repeating-linear-gradient()函数,重复相同的渐变,直到填满整个元素。该函数接受与linear-gradient相同的基本值集,不同之处在于,最后一个颜色停顿点需要指定一个长度或百分比值。下面是一个示例:
E { background-image: repeating-linear-gradient(white, black 25%); }
最后的颜色停靠点值设置了渐变结束的点,并从该点开始重复。此代码创建了一个上下渐变(默认)在白色和黑色之间,覆盖了框体高度的 25%,意味着它重复了四次,如图 11-6 所示。

图 11-6:从白到黑的重复渐变
接下来的代码示例使用了不同的值;图 11-7 展示了结果,我会在你查看完代码后逐一解释每个。
➊ .gradient-1 {
background-image: repeating-linear-gradient(to left, black, white, black 25%);
}
➋ .gradient-2 {
background-image: repeating-linear-gradient(45deg, black, white 2px, black 10px);
}
➌ .gradient-3 {
background-image: repeating-linear-gradient(315deg, black, black 2px, white 2px, white 4px);
}

图 11-7:不同的重复线性渐变
• 第一个示例(➊)使用了三个颜色停靠点并设置了方向,使渐变从右向左。渐变覆盖了元素的 25%,因此黑白黑的模式重复了四次。
• 第二个示例(➋)使用了45deg的角度值,使渐变呈对角线方向,并且颜色停靠点使用了像素单位。同样,渐变为黑白黑,但它们的不均匀分布使得黑白部分覆盖 2px,而白黑部分覆盖 8px。
• 最后的示例(➌)使用了四个颜色停靠点:黑色-黑色覆盖 2px,然后白色-白色覆盖 2px。较小的长度值阻止了两种颜色之间的渐变过渡,形成了你在这里看到的硬直线。
径向渐变
径向渐变是颜色之间的渐变过渡,它从中心点向所有方向扩展。最简单的径向渐变在圆形或椭圆形状中逐渐改变两种颜色。径向渐变通过radial-gradient()值函数来定义,和线性渐变一样,创建径向渐变的最简单方法是将两个颜色值作为参数传递:
E { background-image: radial-gradient(white, black); }
结果是一个简单的双色白黑渐变,呈椭圆形,延伸至其父元素的最远角落,如图 11-8 所示。

图 11-8:一个简单的双色径向渐变
使用径向渐变
你可以通过在颜色停靠点前添加一个关键字来设置径向渐变的形状。默认值是ellipse,但你可以像这样使用替代的circle:
E { background-image: radial-gradient(circle, white, black); }
如图 11-9 所示的结果比图 11-8 更接近圆形,尽管缺乏清晰定义的边缘。(你将很快学到如何设置径向渐变的范围。)

图 11-9:一个圆形的径向渐变
放射性渐变的默认中心(渐变的辐射起点)位于应用该渐变的元素的中心。你可以通过向 radial-gradient() 函数添加位置参数来改变这个点。用于设置该位置的值与 background-position 中使用的完全相同——即长度、百分比或关键字。位置参数加在形状关键字(这里示例中是 circle)后面,前面加上 at 关键字。位置设置为元素的右中心:
E { background-image: radial-gradient(circle at 100% 50%, white, black); }
你还可以设置渐变的范围——即渐变结束的点——使用长度或位置值,或者四个范围关键字之一。范围参数紧跟在形状关键字后面。例如,这段代码创建了一个圆形渐变,范围为 50px,意味着它在距中心点 50px 处结束:
E { background-image: radial-gradient(circle 50px, black, white); }
设置范围时可以使用的四个关键字是 closest-corner、closest-side、farthest-corner(默认)和 farthest-side。下一个示例展示了这些关键字之间的差异,每一行的结果都显示在图 11-10 中。我将依次讨论每条规则。
➊ .ext1 { background-image: radial-gradient(closest-corner circle at 10% 60%, white, black); }
➋ .ext2 { background-image: radial-gradient(closest-side circle at 10% 60%, white, black); }
➌ .ext3 { background-image: radial-gradient(farthest-corner circle at 0% 100%, white, black,
white, black); }
➍ .ext4 { background-image: radial-gradient(farthest-side circle at 0% 100%, white, black,
white, black); }

图 11-10:比较放射性渐变的范围关键字
所有四个渐变都产生相同的圆形效果。在示例 ➊ 和 ➋ 中,两个渐变具有相同的中心位置和颜色停止值,但 ➊ 的范围通过 closest-corner 关键字设置,使渐变的范围是最靠近的角落(左下角),而 ➋ 的范围是最靠近的边(左边,通过 closest-side 设置),因此第二个圆要比第一个小得多。
示例 ➌ 和 ➍ 中的渐变具有相同的中心位置(元素的左下角),每个都有四个颜色停止值(交替的黑白)。示例 ➌ 的范围是最远的角落(右上角,通过 farthest-corner 设置),而示例 ➍ 的范围是最远的边(右边,你现在可能已经猜到,它是通过 farthest-side 设置的)。虽然差别微妙,但你应该能看出,➍ 的范围比 ➌ 更为有限。
使用多个颜色停止值
与线性渐变一样,放射性渐变也可以接受多个颜色停止值以及用于定位控制的长度或百分比值。这些限定符以逗号分隔的列表形式输入。以下示例中的四个示例展示了一些有趣的变化。图 11-11 展示了每一行的结果。
➊ .gradient-1 { background-image: radial-gradient(farthest-side circle, black, white, black); }
➋ .gradient-2 { background-image: radial-gradient(farthest-side circle, black, white 25%,
Because gradients are applied wiblack); }
➌ .gradient-3 { background-image: radial-gradient(farthest-side circle at left, white,
black 25%, white 75%, black); }
➍ .gradient-4 { background-image: radial-gradient(circle closest-side circle at 40% 50%,
white, white 25%, black 50%, white 75%, black); }

图 11-11:不同颜色停止值的放射性渐变
在示例 ➊ 中,我创建了一个包含三个颜色停止值(黑白黑)的渐变,它从盒子的中心辐射到最远的边。示例 ➋ 与之相似,不同之处在于颜色停止值从半径长度的 25% 处开始。
在 ➌ 示例中,渐变从框的左侧开始,到右侧结束,颜色停靠点分别位于长度的 25% 和 75%。在 ➍ 示例中使用了五种颜色,但通过将第一个和第二个颜色停靠点设置为相同颜色,我在中心创建了一个实心白色圆圈。
重复径向渐变
就像 linear-gradient() 函数有 repeating-linear-gradient() 一样,repeating-radial-gradient() 可以用于重复给定的参数,直到最终颜色停靠点所指定的限制为止。例如,以下代码创建了一个圆形渐变,每 20% 重复一次黑色-白色,直到达到其范围。结果显示在图 11-12 中。
E { background-image: repeating-radial-gradient(circle, black, white 20%); }

图 11-12:一个重复的径向渐变
如需更多关于如何使用重复径向渐变的示范,请查看下一段代码。结果显示在图 11-13 中。
➊ .gradient-1 { background-image: repeating-radial-gradient(circle farthest-corner at right top,
black, white 10%, black 15%); }
➋ .gradient-2 { background-image: repeating-radial-gradient(circle farthest-corner at left,
white, white 10px, black 15px); }
➌ .gradient-3 { background-image: repeating-radial-gradient(circle farthest-corner, white,
black 1px, white 2px); }

图 11-13:使用重复径向渐变创建的图案
➊ 示例从右上角发散,并通过 15% 宽度的三个颜色停靠点,限制由 farthest-corner 关键字设定。在 ➋ 示例中,我将渐变的中心设置为框的左侧,限制设定为最远角,使用白色-白色(实心)渐变 10px,然后使用白色-黑色渐变 5px。最后,在 ➌ 示例中,我似乎创造了一件艺术作品!白色-黑色-白色渐变在 2px 的非常小半径上重复,形成了你看到的干涉图案。
浏览器支持与旧语法
尽管渐变历史曲折,但好消息是最终语法已经被各大浏览器很好地实现。Chrome、Firefox、IE10+、Safari 7+(包括移动版)和 Android 4.4+ 都不需要厂商前缀,支持所有相关属性。
WebKit 的旧版本(包括 Safari 5 到 6.1 和 Android 4.0 到 4.3)支持线性渐变的过渡语法,其中渐变的方向由其起始位置定义,而不是结束位置。此规则使用 -webkit- 前缀。以下列表对比了旧的过渡语法与最终语法;两者的输出结果相同。
.old { background-image: -webkit-linear-gradient(left center, black,
white); }
.new { background-image: linear-gradient(to right center, black, white); }
如果你需要支持更早版本的 WebKit(Android 3.0 和 iOS 4.3 及更早版本),你将遇到原始语法,该语法因过于复杂而被其他浏览器厂商拒绝。在这个原始语法中,线性和径向渐变都使用 gradient() 值函数定义,并且参数不同。以下列表展示了使用这种旧语法可能的最简单的线性和径向渐变:
E { background-image: -webkit-gradient(linear, 50% 0, 50% 100%,
from(black), to(white)); }
E { background-image: -webkit-gradient(radial, 50% 50%, 0, 50% 50%, 100,
from(black), to(white)); }
说实话,解释这里发生的事情需要花费很长时间,尤其是对于径向渐变的语法。我的最佳建议是,根本不考虑使用这种原始语法,而是为元素提供一个优雅的后备方案,比如使用单一的背景色或图片。这样你能避免很多麻烦。
多个渐变
由于渐变是通过background-image属性应用的,你可以使用 CSS3 的多重背景值语法(参见第八章)来使用逗号分隔的值为元素应用多个渐变。接下来的示例展示了两个例子,第一个使用线性渐变,第二个使用径向渐变。图 11-14 展示了每个例子的结果。
.linears {
background-image:
linear-gradient(to right bottom, black, white 50%, transparent 50%),
linear-gradient(to left bottom, black, white 50%, black 50%);
}
.radials {
background-image:
radial-gradient(closest-side circle at 20% 50%, white, black 95%, transparent),
radial-gradient(closest-side circle at 50% 50%, white, black 95%, transparent),
radial-gradient(closest-side circle at 80% 50%, white, black 95%, transparent);
}

图 11-14:多个渐变背景值
左侧的示例展示了两个线性渐变应用于一个元素:从左上角到右下角,以及从右上角到左下角。最后的颜色停靠点的值为transparent,以便第二个渐变能穿透并显示在其下方。(记住,如果不设置透明度,渐变会填充整个框并遮住下面的层。)右侧的示例展示了三个径向渐变,每个渐变都延伸到最近的边,且每个渐变的中心位于不同的位置。与第一个示例一样,最后的颜色停靠点的值为transparent,以便让下面的层显示出来。
总结
尽管 CSS 渐变有很多历史问题,但最终的语法相当优雅,能够提供一系列美丽的背景效果。想要了解更多可能性,请访问 Lea Verou 的 CSS3 Patterns Gallery,网址是* lea.verou.me/css3patterns/*,这里展示了一些真正突破极限的效果——不过,请记住本章开始时关于性能的警告,因为这些示例可能会对你的移动浏览器造成较大负担。
此外,在我写这段文字时,计划正在进行中,旨在通过一种新的渐变类型conic扩展可能性,这将带来更加令人兴奋的效果。
现在我们已经完成了这一小段讲解页面元素装饰的章节,接下来的章节将进入一个全新的领域:页面元素的转换与动画。
渐变:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 线性渐变 | 是 | 是 | 是 | IE10 |
| 重复线性渐变 | 是 | 是 | 是 | IE10 |
| 径向渐变 | 是 | 是 | 是 | IE10 |
| 重复径向渐变 | 是 | 是 | 是 | IE10 |
第十二章:12
2D 变换

由于 HTML 的工作方式,每个元素都是由矩形块和直角构成的,网页通常显得很方正,有很多直的水平和垂直线,唯一能打破这种规则的方法就是使用图片。但早在 2008 年,WebKit 团队提出了一个新模块,允许元素进行旋转、缩放、倾斜等各种操作。该模块被 W3C 采纳并正式化为 2D 变换模块(www.w3.org/TR/css3-2d-transforms/)。
变换元素的大部分过程是从可缩放矢量图形(SVG)语言中的函数借鉴而来的,SVG 用于绘制二维矢量图像。大多数现代浏览器都支持 SVG,因此 Firefox 和 Opera 迅速在自己的产品中实现了 2D 变换,IE9 也很快跟进。
CSS 和 SVG 的变换属性非常相似,事实上,W3C 决定将它们合并为一个共同的规范——CSS 变换(dev.w3.org/csswg/css-transforms/),这就是该模块今天仍在继续工作的地方。简而言之,尽管 CSS 变换模块仍然只是一个工作草案,但其中的属性已经得到很好的实现,您可以立即开始使用这些新特性。
在我介绍语法之前,有一点需要注意:在本章结束时,我将介绍一些相当复杂的函数,因此您可能需要复习一下三角学。你紧张吗?别担心,我会尽量让它变得简单易懂。
transform 属性
可以应用各种不同的变换,但所有变换都作为transform属性中的函数声明。以下是基本语法:
E { transform: function(value); }
有许多可用的函数;我将在本章的其余部分依次介绍每个函数。每个函数都接受一个单独的值或一个用逗号分隔的值列表。当我分别讨论每个函数时,我也会解释这意味着什么。
您可以通过在transform属性中简单地列出多个函数(以空格分隔)来应用多个变换到单个元素:
E { transform: function(value) function(value); }
在使用transform属性时,您需要注意一个非常重要的警告,但在我谈论这个之前,我需要介绍各种函数。然而,我强烈建议您不要跳过在第 144 页上的“关于变换函数的重要说明”。
rotate
所有函数中可能最简单的就是rotate(),它做的正如字面意思所示:它围绕某一点旋转元素。以下是语法:
E { transform: rotate(value); }
这里的*value*是一个单一的角度值,就像你在第十一章中使用 CSS 渐变时一样。而且,像在那一章中一样,我的示例仍然采用通常理解的角度单位(deg)。
为了向你展示rotate()的效果,我将使用这个规则将h2元素旋转-15 度(或 345 度):
h2 { transform: rotate(-15deg); }
浏览器对 2D 变换的支持
正如我在本章开始时提到的,浏览器对 2D 变换的支持已经相当普及,尽管并非所有浏览器都支持没有厂商前缀的transform属性。IE9、Safari 和较老版本的 Android 浏览器都需要前缀,这意味着目前要使用这个属性,你必须指定三次:
E {
-ms-transform: function(value); /* IE9 */
-webkit-transform: function(value); /* WebKit */
transform: function(value);
}
你可以在图 12-1 中看到这个示例的展示效果。请注意,我将另一个背景为浅灰色的h2元素放置在与第一个元素相同的位置,这样你可以比较旋转后的元素和默认(未旋转)元素。我将在本章的大多数示例中使用相同的方法。

图 12-1:一个旋转了-15 度的元素
文档流中的位置
在我继续之前,有一个重要的点需要说明:变换后的元素仅影响页面的视觉呈现,而不影响文档布局。该元素本身仍然保留在文档流中,因此所有后续的元素都会受到它的影响,包括它的边距和内边距。变换后的元素不会影响页面布局,但看起来像是置于页面其他部分之上的新层,这意味着它可能会覆盖随后的元素。
为了说明我的意思,我将再旋转一个元素,这次在其下方加上一些文本,方便你看到变换效果。这里是旋转元素的代码:
h2 { transform: rotate(-10deg); }
图 12-2 展示了这个变换的结果,以及一个未变换的参考。两个示例是完全相同的,只是应用了rotate()函数。你可以清楚地看到,旋转后的元素覆盖了其下方的文本,而这些文本紧随未变换的元素之后出现在布局中。这个规则适用于任何应用了transform属性的元素,因此在继续操作时要注意这一点。

图 12-2:变换元素对文档流的影响
transform-origin
变换的原点是元素上进行变换的那个点。这个概念最容易通过rotate()来说明,尽管你可以将其应用到本章后面介绍的任何变换函数。
对于rotate(),你可以通过想象你有一张纸(元素)和一个针(元素的原点)来直观地理解原点。如果你用针把纸固定在一个平面上,你就可以围绕针旋转纸张。通过将针固定在纸上的不同位置,你可以改变旋转的方式——如果针固定在纸的中心,旋转半径较短,纸张两侧的移动相等;如果针固定在纸的边缘,整个纸张会围绕针旋转。图 12-3 展示了这种情况。

图 12-3:围绕针旋转纸张:移动针的位置会改变旋转点。
CSS transform属性的默认原点是元素的水平和垂直中心。你可以通过transform-origin属性来更改它:
E { transform-origin: value; }
此属性的*value*可以是一个或两个长度、百分比或关键字值。长度可以是任何接受的 CSS 单位(em、px 等)。关键字有left、right、top、bottom和center。如果提供两个值,第一个设置水平点,第二个设置垂直点;如果只提供一个值,该点设置为水平点,垂直点默认为center(或 50%)。
所以,如果你想将原点更改为左上角,可以使用以下任意值:
E { transform-origin: 0 0; }
E { transform-origin: left top; }
如果你想将原点设置为右下角,可以使用这些值(假设元素的height为 50px,宽度为 200px):
E { transform-origin: 200px 50px; }
E { transform-origin: 100% 100%; }
E { transform-origin: right bottom; }
让我展示一下改变变换原点的效果。这个例子展示了三个相同的元素,每个元素都有相同的transform属性,但transform-origin值不同:
h2 { transform: rotate(-10deg); }
h2.example-1 { transform-origin: left center; }
h2.example-2 { transform-origin: 100% 50%; }
你可以在图 12-4 中看到这三种例子的效果。第一个例子使用默认值(center center),因此元素围绕绝对中心旋转。第二个例子使用left center的值,因此元素围绕左侧的垂直中心旋转。第三个例子使用100% 50%的值,因此元素围绕右侧的垂直中心旋转。

图 12-4:不同的 *transform-origin* 值对旋转元素的影响
translate
接下来我们要讲解的函数是translate,它将元素从默认位置沿水平或垂直轴移动(如果你需要复习,请参考第六章“理解轴和坐标”,第 64 页)。沿水平轴的移动由translateX()函数控制,沿垂直轴的移动由translateY()函数控制:
E { transform: translateX(value) translateY(value); }
这两个函数translateX()和translateY()分别沿着指定的轴移动元素,并且根据你指定的长度进行移动。你可以在这里使用任何长度单位或百分比值,例如,你可以使用:
E { transform: translateX(20px) translateY(15%); }
这段代码将元素向右移动 20px(沿* x 轴)并将其自身高度的 15%向下移动(沿 y *轴)。你可以在图 12-5 中看到这个结果。

图 12-5:元素沿两个轴正向平移
你也可以在translate函数中使用负值,这会使元素沿着轴的相反方向移动——也就是说,向上或向左。
注意
你可能认为平移元素与使用相对定位以及*left*和*top*属性类似,但请记住,转换后的元素仍然保留在文档流中,只是看起来像是移动了;元素的视觉渲染被转换了,而不是元素本身。
translate()简写函数
更方便地沿两个轴移动元素的方法是使用简写函数translate()。该函数接受一个以逗号分隔的最多两个值:
E { transform: translate(translateX(),translateY()); }
鉴于此,你可以使用简写函数和两个参数来复制图 12-5 中所示的转换:
E { transform: translate(20px,15%); }
你还可以只使用一个值来使用translate()简写:
E { transform: translate(20px); }
在这种情况下,提供的值被认为是translateX()的值,而translateY()则使用0(零)。因此,以下两种声明是等价的:
E { transform: translate(20px,0); }
E { transform: translate(20px); }
使用 translate()函数
为了说明translate()简写函数的工作原理,下面是两个示例:
h2.translate-1 { transform: translate(-20px,20px); }
h2.translate-2 { transform: translate(20px,-20px); }
你可以在图 12-6 中看到这段代码的结果,元素以浅灰色显示,表示原始位置。

图 12-6:元素显示*translate()*函数不同值的效果
在第一个示例中,translate()函数的第一个参数是−20px,第二个参数是 20px,因此元素在水平方向上负向平移 20px(向左),在垂直方向上正向平移 20px(向下)。第二个示例的参数相反,因此元素在水平方向上正向平移(向右),在垂直方向上负向平移(向上)。
缩放
你可以使用scale函数将元素的大小调整为比原始尺寸更大或更小。与translate函数一样,水平方向和垂直方向各自有一个函数,分别称为scaleX()和scaleY():
E { transform: scaleX(value) scaleY(value); }
scaleX()和scaleY()的value是无单位的数字,表示大小比率。默认大小为 1;两倍于默认值为 2,默认值的一半为 0.5,依此类推。你还可以使用负数——我将很快解释负数的效果。
要将元素的原始尺寸在两个轴上都加倍,可以使用:
E { transform: scaleX(2) scaleY(2); }
也可以使用简写函数scale()。但请注意,不同于translate()简写函数的是,如果只提供一个数值,另一个数值会被假定为相同。根据之前的示例,你可以选择简写如下:
E { transform: scale(2); }
我将通过几个示例演示scale的作用。以下是代码:
h2.transform-1 { transform: scale(0.5); }
h2.transform-2 { transform: scaleX(0.5); }
h2.transform-3 { transform: scale(1,-1); }
结果如图 12-7 所示。第一个示例中scale值为 0.5,所以变换后的元素大小是原始尺寸的一半——记住,我在简写中只指定了一个值,因此另一个值被假定为相同。在第二个示例中,我使用了 0.5 作为scaleX()函数的值,意味着变换后的元素高度与原始元素相同,但宽度是原始的一半。

图 12-7:不同数值在*scale*函数中的效果
在最后一个示例中,我为scale()简写提供了两个值:第一个是1(单位),表示水平尺寸与原始尺寸相同,而第二个是–1(负一)。使用负值会使元素在垂直方向上翻转,创建出一个与原元素等比例的“镜像”。
skew
skew一个元素是改变其水平或垂直轴(或两个轴)的角度。像translate()和scale()一样,每个轴都有一个单独的函数——这次是skewX()和skewY()。
E { transform: skewX(value) skewY(value);}
skew函数的参数是角度值;在我的示例中使用的是度数。负数值也是允许的。我将通过三次演示,使用以下代码向你展示它们的工作原理:
h2.transform-1 { transform: skewX(15deg); }
h2.transform-2 { transform: skewY(5deg); }
h2.transform-3 { transform: skewX(15deg) skewY(-15deg); }
结果如图 12-8 所示。

图 12-8:不同数值作用下元素的变换效果*skew*函数
在第一个示例中,元素沿其X轴倾斜了 15 度,导致垂直边缘呈斜角。第二个示例中,元素在Y轴上倾斜了 5 度,因此水平边缘呈斜角,而垂直边缘保持不变。最后一个示例展示了应用两个数值的效果:X轴 15 度,Y轴−15 度,导致元素在两个轴上都发生倾斜。
通过 skew 进行旋转
通过查看前一个代码块中的最后一个示例,你可以看到使用skew是可以复制rotate()函数的。为此,你希望旋转元素的角度被作为skewX()的值,而skewY()则是该角度的逆值;也就是说,如果skewX()是 15 度,那么skewY()应该是−15 度,反之亦然。因此,下面这个代码示例中的两个函数完成的是相同的操作:
E {
transform: rotate(15deg);
transform: skewX(15deg) skewY(-15deg);
}
当我在本章稍后介绍matrix()函数时,你会发现这些信息非常有用。
skew()简写函数
CSS 变换模块和各种在线资源也列出了一个简写skew()函数,它接受两个单独的skew函数作为值(就像translate()接受translate函数一样):
E { transform: skew(skewX(),skewY()); }
然而,我强烈建议你不要使用这个函数,因为出于某些我不理解的数学原因,它被认为是多余的。它仅为向后兼容而定义;在冗余被发现之前,一些旧版浏览器实现了该简写函数。如果你需要对两个轴进行倾斜,请使用skewX()和skewY()。
关于变换函数的重要说明
当你设置transform属性的值时,任何未列出的函数将被假定重置为其默认值。为了说明这一点,看看这个 CSS 代码片段,其中一个规则应用于div选择器来旋转和缩放它,另一个规则应用于*.foo*选择器,以不同的值参数旋转它:
div { transform: rotate(5deg) scale(2); }
.foo { transform: rotate(10deg); }
如果这些规则应用于一个类名为.*foo*的div元素,则该元素只会旋转 10 度,而不会缩放;因为没有指定scale()函数,所以它的值参数被视为默认值0deg。要应用这两个属性,你必须更新代码,在应用于*.foo*的规则中包含scale()函数:
div { transform: rotate(5deg) scale(2); }
.foo { transform: rotate(10deg) scale(2); }
使用矩阵变换元素
本章迄今为止使用的每个变换函数也可以表示为变换矩阵。我不会解释变换矩阵背后的理论(因为它相当复杂,足以单独成章),我只是展示如何通过matrix()函数将其应用于 CSS 中的元素。
我会尽量保持解释简单,只提供实用的基础知识。如果你真想深入了解理论,建议阅读 W3C 在 SVG 1.1 模块中的解释(www.w3.org/TR/SVG/coords.html#TransformMatrixDefined/)。
2D 变换矩阵和matrix()函数接受六个值,它们的组合可以用来创建本章已介绍的变换函数。其语法如下:
E { transform: matrix(a,b,c,d,X,Y); }
所有默认值都是 0(零),并且它们的行为会根据提供的值有所不同——我会在后续中解释这意味着什么。我说过,你可以通过matrix()执行本章介绍的所有函数,但过程并不那么简单——你首先需要了解一些三角学知识。
在处理复杂内容之前,我会从一个简单的内容开始,它不需要任何三角函数:缩放。如果你想缩放一个元素,可以使用*a*和*d*来等同于scaleX()和scaleY()并设置相应的值,同时将*b*和*c*设为 0。因此,要将元素的大小加倍,你可以使用:
E { transform: matrix(2,0,0,2,0,0); }
你还可以通过为*X*和*Y*(分别是水平方向和垂直方向)提供偏移值,使用matrix()来转换一个元素。这些值是无单位的数字,表示像素值(在 HTML 元素中是像素值,在 SVG 中则是向量点值)。因此,如果要将一个元素的大小加倍并同时将其垂直和水平方向偏移 15px,可以使用以下代码:
E { transform: matrix(2,0,0,2,15,15); }
如果你想使用matrix()来倾斜一个元素,那么这里就变得更加复杂了——在这里我需要引入三角函数。你可以在维基百科上阅读这些函数的完整解释(* en.wikipedia.org/wiki/Trigonometric_functions#Sine.2C_cosine_and_tangent/*),但下面是一个快速而简略的总结:三角函数是用于计算三角形角度的比值。
我将使用的第一个三角函数是tan(正切),它用于沿x-轴或y-轴倾斜一个元素。参照原始的matrix语法,x-轴作为值传递给*b*,y轴作为值传递给*c*。以下是每个的语法:
E { transform: matrix(1,tan(angle),0,1, X,Y); } /* X Axis */
E { transform: matrix(1,0,tan(angle),1, X,Y); } /* Y Axis */
*angle*指的是你希望倾斜的角度(逆时针方向),单位是度。如果你想将元素倾斜 15 度,那么你需要的值就是 15 度的正切值。因此,拿出你的科学计算器——如果没有,可以使用操作系统自带的科学计算器——计算出tan(15)的结果为 0.27。这个结果就是你需要传递给matrix函数的值。例如,如果你希望倾斜是沿着x-轴的,那么你可以使用以下语法:
E { transform: matrix(1,0.27,0,1,0,0); }
注意 由于我在示例中使用的是度数,请确保你的计算器的三角函数模式设置为度数,如果你想要跟着示例走。如果你更喜欢使用弧度或格拉度,可以相应地调整这些示例。
如前所述,倾斜函数也可以用来旋转元素——你也可以用matrix()来实现这一点。这一次,你需要使用sin(正弦)和cos(余弦)三角函数。旋转元素的matrix()语法是:
E { transform: matrix(cos(angle),sin(angle),-sin(angle),cos(angle),X,Y); }
请注意,*a*和*d*的值相同,而*b*和*c*则取相反值(如果*b*是正值,则*c*是相同值的负值,反之亦然)。再次说明,*angle*指的是你希望旋转元素的角度。如果要旋转 60 度,回到你的科学计算器,计算 60 度的余弦和正弦。我的计算器显示cos(60) = 0.5和sin(60) = 0.87,所以所需的代码是:
E { transform: matrix(0.5,0.87,-0.87,0.5,0,0); }
现在让我们看几个示例。这是我将使用的代码:
h2.transform-1 { transform: matrix(1,0,0,-1,0,0); }
h2.transform-2 { transform: matrix(1,0,0.268,1,-10,-20); }
h2.transform-3 { transform: matrix(0.98,-0.17,0.17,0.98,0,0); }
输出结果如图 12-9 所示。

图 12-9:使用*matrix*函数进行变换的示例
在第一个例子中,我将元素垂直翻转,正如之前使用 scale() 在 图 12-7 中做的那样。在下一个例子中,我沿 y 轴将元素倾斜了 15 度(通过计算 tan(15) = 0.268),并沿两个轴平移它。最后一个例子展示了将元素旋转了 10 度;这些值是我之前给你展示过的计算结果:cos(10) = 0.98 和 sin(10) = 0.17。如前所述,sin 值在位置 *b* 为负,在位置 *c* 为正,这样旋转效果就保持一致。
我知道这些内容都相当复杂,但希望我已经简化到足够让你理解的程度,同时又不至于显得太简单,以至于你看不到其中极大的灵活性——只要你随时携带科学计算器!如果觉得这些内容过于复杂,理解起来有困难,记住,你可以使用各个独立的函数来执行所有这些变换,所以如果你愿意,完全可以忽略 matrix 和三角函数。
总结
本章介绍了迄今为止最复杂的属性:matrix() 函数。虽然有些人可能会觉得,想要执行复杂操作就必须应对复杂的语法,但我认为本章中的其他函数做得很好,将复杂性转化为简单的内容。只要记住,如果你厌烦了 matrix(),总有更简单的选项可以选择。
本章还介绍了一些至今为止最具革命性的属性——几年前,元素可以旋转、倾斜和缩放的概念还只是空想,但如今你可以轻松做到这些。如果你觉得本章所展示的内容已经令人印象深刻,那就等着下一章吧。我将向你展示如何将一个额外的维度——字面意义上的——添加到你的变换效果中。
二维变换:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 二维变换 | 是 | 是 | 是^(*) | IE9*,IE10 |
- 使用厂商前缀
第十三章:13
3D 变换

到目前为止,我在本书中讨论的所有功能和特性都涉及了二维;每个元素都有高度和宽度,所有的计算仅涉及 x 轴和 y 轴。但随着 CSS 变换模块中引入 z 轴,CSS3 提供了一种革命性的方法来对元素进行三维变换(你可以在 www.w3.org/TR/css-transforms-1/ 上了解更多)。
当我写这本书的第一版时,只有 Safari(适用于 Mac 和 iOS)支持 3D 转换,但现在仅仅几年后,所有主流浏览器都已稳定实现了这一功能,包括 IE10 及以上版本——这是一个极为快速的采用速度。写这篇文章时,在 Safari 中,你需要对所有属性添加 -webkit- 前缀,但其他浏览器没有要求使用厂商前缀。
在三维空间中移动物体的工作可能非常消耗处理器资源,但大多数(如果不是所有的话)浏览器已经实现了所谓的 硬件加速,直接在设备的图形芯片上进行所需的计算,而不是在浏览器的软件中或主处理器上进行。这意味着,3D 变换的元素通常比仅使用 JavaScript 动画的其他元素更平滑、性能更好。鉴于此,你可以放心地在页面中使用 3D 变换,而不必太过担心。
CSS 中的 3D 元素
CSS 中的三维对象基于 笛卡尔坐标系,如 图 13-1 所示。你可以在 Wikipedia 上了解更多 (en.wikipedia.org/wiki/Cartesian_coordinate_system/)。我在 第六章 中讨论了该系统的二维版本。

图 13-1:笛卡尔坐标系,包含轴 x、y* 和 z^(1)*
注意
如果你有使用三维计算机图形程序的经验,你应该熟悉本章中使用的计算方法和术语。如果没有,不用担心;我会尽力在过程中解释清楚所有内容。
在 CSS 中,z 轴是基于观察者的:如果你把 x 轴想象为左右,y 轴想象为上下,那么 z 轴就是朝向或远离观察者。当你通过正值沿 z 轴移动一个元素时,元素会向你靠近;同样,使用负值则会将其推离你。你可以通过使用不同的视角来稍微改变这一点,稍后我会详细解释。
我必须指出,尽管本章会讲很多关于 3D 的内容,但我所指的仅仅是三维坐标。元素本身仍然是二维的,它们只有高度和宽度。然而,它们在三维空间中被移动,就像把一张纸在空气中移动一样。
在印刷页面上展示三维变换概念相当困难,因此我强烈建议你查看本书附带的网站上的示例(http:/www.thebookofcss3.com/);我已经为其中一些示例添加了过渡规则,让它们在交互时自动动画,从而更清晰地展示那些在二维书籍中难以呈现的三维效果。这些示例应该有助于你理解本章中使用的一些技巧。我还推荐由 Westciv 团队整理的测试页面(www.westciv.com/tools/3Dtransforms/)。这个网站允许你尝试不同的变换值组合,看看它们对元素的影响。
在我介绍新的和扩展的变换属性之前,先简要说明一下本章使用的示例。尽管每个示例可能使用不同的类来应用独特的样式规则,但所有示例都使用相同的基本标记:
<div class="d3-parent">
<div class="d3-child">
<h2>The Book Of</h2>
<h1>CSS3</h1>
</div>
</div>
大部分变换工作是在.d3-child元素上执行的,尽管有些属性必须应用于父元素,这就是为什么我还需要.d3-parent元素。我会在本章讲解时明确说明哪些属性应用于父元素,哪些应用于子元素。
变换函数
在第十二章中,我介绍了transform属性及其相关函数,用于二维变换。三维变换使用相同的属性和许多相同的函数,但也扩展了一些现有函数,并增加了一些全新的函数。我将逐一解释每个变换函数,并指出它们是全新的,还是扩展了我已经讨论过的二维变换属性。请注意,transform属性始终应用于子元素(在示例标记中为.d3-child)。
围绕轴的旋转
我将像解释二维函数一样解释三维变换函数——从旋转开始。二维空间只有一个轴来旋转,因此rotate()函数只需要一个值。但是当你处理三维空间时,你有三个轴可以旋转,因此需要控制三个属性来实现旋转。
E {
transform: rotateX(angle);
transform: rotateY(angle);
transform: rotateZ(angle);
}
像rotate()函数一样,每个函数接受一个单一的角度值(允许负值)。我在示例中使用deg(度)单位,并通过以下代码展示如何在每个轴上使用相同的旋转角度:
➊ .trans-x { transform: rotateX(45deg); }
➋ .trans-y { transform: rotateY(45deg); }
➌ .trans-z { transform: rotateZ(45deg); }
你可以在 图 13-2 中看到结果。第一个示例(➊)展示了围绕 x 轴旋转的元素。为了理解这里发生了什么,想象有一条横向穿过元素中心的线;元素上半部分相对于这条线倾斜 45 度远离你,下半部分则相对于这条线倾斜 45 度朝向你。下一个示例(➋)是在 y 轴上进行旋转。想象有一条纵向穿过盒子中心的线;线左侧的元素部分向你倾斜 45 度,右侧部分则远离你倾斜 45 度。最后一个示例(➌)则是在 z 轴上进行旋转,这与二维的 rotate() 函数效果相同。

图 13-2:围绕三个轴各旋转 45 度
如果你想让一个元素围绕多个轴进行旋转,可以对元素应用多个函数:
E { transform: rotateX(angle) rotateY(angle) rotateZ(angle); }
另一个新功能——rotate3d()——也允许你围绕多个轴旋转一个元素;然而,它并不是一个简写函数。下面是语法:
E { transform: rotate3d(x,y,z,angle); }
*angle* 值比较简单,但 *x*、*y* 和 *z* 值就没有那么简单了。每个值都需要一个数字,用来计算一个方向向量(其完整解释超出了本书的范围;你可以访问 mathworld.wolfram.com/CartesianCoordinates.html 了解相关概述)。向量的原点是所有轴交汇的点——默认为元素的中心——用 0,0,0 表示。方向向量是三维空间中的一条线,从原点到由 *x, y, z* 值指定的坐标。元素将围绕这条线进行旋转,旋转的角度由 *angle* 值指定。
这个主题比较复杂,所以我将通过几个简单的示例来解释,再次使用相同的 45 度旋转,代码如下:
➊ .trans-x { transform: rotate3d(1,1,0,45deg); }
➋ .trans-y { transform: rotate3d(1,0,1,45deg); }
➌ .trans-z { transform: rotate3d(0,10,10,45deg); }
你可以在 图 13-3 中看到输出。第一个(左侧)示例(➊)的值为 1,1,0,意味着假想的线段指向 x 轴和 y 轴上各 1px 的位置。(实际上,“线”会沿着这个方向继续延伸;如果值是 10,10,0 或 1000,1000,0,也会产生相同的效果。)该元素围绕这条线旋转了 45 度。第二个(中间)示例(➋)的值为 1,0,1,在 x 轴和 y 轴上创建了一个 1px 的点,并围绕这条线旋转了 45 度。最后一个示例(➌),位于右侧,值为 0,10,10,因此元素围绕原点和 y 轴与 z 轴交点之间的线旋转了 15 度。记住,任何两个相同的值都会产生相同的效果。

图 13-3:使用方向向量与 *rotate3d* 函数进行旋转
在编写纯 CSS 变换时,你可能不会频繁使用rotate3d()函数,因为涉及的计算相当复杂。但当结合 JavaScript 的数学处理能力时,这种方法的灵活性确实可以展现其强大作用。
透视
下一个函数,perspective(),实际上是最重要的之一,因为它从一个人工的视角创建你从三维空间中查看对象的效果,提供深度的错觉。如果没有指定,元素会对观众看起来“平坦”,无论应用了其他什么变换函数(除非从父元素继承了透视;详情请见第十三章的“perspective和perspective-origin属性”,参见 159 页)。以下是语法:
E { transform: perspective(depth); }
值*depth*是一个长度单位或者默认关键字none。这个长度设置一个“视点”,该视点沿着z轴与元素原点(*z* = 0)保持一定距离。一个较小的深度值——比如 50px——会让元素看起来非常接近观众,尺寸被夸大;而大约 1000px 的值可以被认为是“正常”的。
perspective()函数可能更容易展示而不是描述。在下一个示例中,我使用不同的值来展示该函数,向你展示各种值如何改变视角……呃,视距。以下是代码:
➊ .trans-1 { transform: perspective(1000px) rotateX(30deg) rotateY(45deg); }
➋ .trans-2 { transform: perspective(250px) rotateX(30deg) rotateY(45deg); }
➌ .trans-3 { transform: perspective(150px) rotateX(30deg) rotateY(45deg); }
警告 当在 *transform* 属性上使用多个函数时,*perspective()*函数必须始终列在第一个;如果列在其他函数之后,它将被忽略。
你可以在图 13-4 中看到代码的结果。在左侧示例(➊)中,元素从perspective()的 1000px 距离观看。正如我所说,这是“正常”的距离;元素围绕两个轴旋转,但看起来尺寸比例正确。在中间的示例(➋)中,perspective()值降至 250px,使元素看起来在比例上被夸大,因为我将视点靠近了元素。最后(右侧)示例(➌)显示了一个perspective()值为 150px 的元素,这意味着你实际上从 150px 的位置沿z轴查看它,使得元素看起来相当夸张。

图 13-4:不同的 *perspective()* 函数值的效果
在继续之前,我想稍微偏离一下话题。你可能会想,为什么代码里有这么多重复?为什么我不能像这样做呢?
.d3-child { transform: rotateX(30deg) rotateY(45deg); }
.trans-1 { transform: perspective(20px); }
.trans-2 { transform: perspective(50px); }
.trans-3 { transform: perspective(1000px); }
原因是,如果我没有指定一个函数,它的值会被默认假定为默认值,因此我在h1元素上的函数设置会被后续样式中(没有函数的)值所覆盖。我在第十二章的“关于变换函数的重要说明”部分解释了这一点。
沿轴平移
translateX()和translateY()函数(及其简写形式translate())用于在二维平面上按指定长度移动元素,但进入三维后需要一个新函数:translateZ()。其语法与兄弟属性相同:
E { transform: translateZ(length); }
*length*值是带有长度单位的任意数值。例如,要将元素沿z-轴移动 30px(向观察者方向),可以使用以下代码:
E { transform: translateZ(30px); }
现在你已经了解了新函数,让我们来看它们的实际应用。在下面的示例中,我展示了两个样式相同的元素,只是translateZ()函数的值不同:
➊ .trans-z1 { transform: translateZ(-40px); }
➋ .trans-z2 { transform: translateZ(40px); }
你可以在图 13-5 中查看结果——请注意,我稍微调整了父元素的角度,并使元素透明,这样你就可以更容易地看到效果。在第一个例子(➊,左侧)中,translateZ()函数的值为−40px,使其沿z-轴负方向移动,显得比父元素小。接下来(➋,右侧)translateZ()的值为 40px,沿z-轴正方向移动,并显得比父元素大。
注意
当你看到元素动画效果时,这个概念会变得更容易理解,所以我再次鼓励你查看随附的示例文件,以便更好地感受这些函数的行为。

图 13-5:沿z-轴不同方向的平移
还有一个简写函数translate3d(),它也可以使用。这个简写形式允许你同时设置三个值。其语法是translate()函数的逻辑扩展:
E { transform: translate3d(translateX,translateY,translateZ); }
每个参数相当于命名函数,因此每个参数都接受一个数值,可以是正数或负数,带有 CSS 长度单位或百分比,示例如下:
E { transform: translate3d(0,100%,1em); }
缩放
我在第十二章中还介绍了scale()函数,以及子函数scaleX()和scaleY()。三维的引入增加了一个新的子函数scaleZ(),其语法如下:
E { transform: scaleZ(number); }
与其兄弟函数一样,*number*值为元素提供一个缩放因子,因此值为 2 时,元素在z-轴上的大小会加倍。然而,结果可能不是你预期的那样;如你所记得,元素本身没有深度,因此单独增加scaleZ()并不会改变元素。实际发生的情况是,它作为translateZ()所提供的任何值的乘数。例如,考虑以下代码:
E { transform: scaleZ(3) translateZ(10px); }
scaleZ()函数的值为 3,乘以translateZ函数的值 10px,因此元素沿z-轴显示为 30px(3 × 10px)。
除了scaleZ(),还新增了一个简写函数scale3d()。其语法如下:
E { transform: scale3d(scaleX,scaleY,scaleZ); }
如你所见,这个简写形式仅接受每个值的数字,作为相关轴上的缩放因子。以下是两个例子,展示 3D 缩放效果:
➊ .trans-z1 { transform: scaleZ(2.5) translateZ(-10px); }
➋ .trans-z2 { transform: scale3d(1.25,1.25,4) translateZ(10px); }
结果显示在图 13-6 中。第一个(左边)示例(➊)展示了一个translateZ()值为−10px 且scaleZ()值为 2.5 的元素;如我所提到的,scaleZ()是translateZ()的倍数,因此该元素沿z轴负向移动了 25px。在第二个示例(➋)中,我使用了scale3d函数将x轴和y轴的值设置为 1.25,将z轴的值设置为 4。结果的元素如右图所示,在二维轴上大了 25%,而scaleZ()值则将translateZ()的 10px 值放大,导致元素沿z轴移动了 40px。

图 13-6:在不同和多个轴上的缩放
变换矩阵
我在第十二章中介绍了 2D 变换的一些较为深奥的方面,其中之一就是matrix()函数。这个函数允许你使用六个值(基于网格模式)和一些三角函数计算来应用复杂的变换。你还可以使用matrix3d()函数应用 3D 变换。但如果你觉得 2D 矩阵难以理解,可能需要跳过这个—matrix3d()有多达 16 个值!以下是语法:
E { transform: matrix3d(
m01,m02,m03,m04,
m05,m06,m07,m08,
m09,m10,m11,m12,
m13,m14,m15,m16
); }
注意
这里显示了换行符以便于理解;实际使用中你不需要使用它们。
每一个*m*值都是一个数字,但我甚至无法开始解释它们每个的作用!我建议你先阅读相关的入门介绍(比如dev.opera.com/articles/understanding-the-css-transforms-matrix/是一个不错的起点),然后决定这是否是你想深入了解的内容。
这里,我提供了一些简单的示例,使用这些代码来展示其功能:
➊ .trans-1 { transform: matrix3d(1,0,0,0,0,1,0,0,0,0,1,0,10,10,10,1); }
➋ .trans-2 { transform: matrix3d(1.5,0,0,0,0,1.5,0,0,0,0,2,0,0,0,10,1); }
➌ .trans-3 { transform: matrix3d(0.96,-0.26,0,0,0.26,0.96,0,0,0,0,1,0,-10, 0,20,1); }
你可以在图 13-7 中看到结果。第一个(左边)示例(➊)展示了元素在每个轴上移动了 10px,相当于translate3d()函数的效果——矩阵中的*m13*、*m14*和*m15*值分别代表translateX()、translateY()和translateZ()。在第二个示例(➋)中,我将图像在x轴和y轴上按 1.5 倍放大(*m1*和*m6*值),在z轴上按 2 倍放大(*m11*值),这会将translateZ()的值(*m15*)放大,导致元素沿z轴移动 20px,如中间示例所示。最后一个示例(➌)需要使用科学计算器来处理一些三角函数,因为我将元素在z轴上旋转了 15 度。要创建旋转,你需要给*m1*和*m6*赋值为 cos(15),即 0.96,然后将 sin(15),即 0.26,赋给*m5*,负的 sin(15)赋给*m2*。我还通过在*m13*中使用值将元素沿x轴平移了 10px。你可以在右侧的图中查看结果。

图 13-7:使用*matrix3d()*函数应用的变换
如你所见,这个函数功能非常强大——也相当复杂。你是否想深入了解matrix3d()的全部潜力,很大程度上取决于你和你的项目,但我认为这超出了本书的范围。别忘了,你可以通过单独的函数执行所有这些变换;虽然最终代码会更多,但它会更容易理解——不仅对你自己,对后续维护你网站的任何人也更容易理解!
perspective 和 perspective-origin 属性
在本章早些时候,我介绍了perspective()变换函数,但perspective属性也是可用的。其语法相当简单:
E { perspective: depth; }
该属性的操作方式与函数相同:*depth*值是一个长度单位,用于设置元素原点与z = 0 之间的距离。实际上,函数与属性之间唯一的区别是,属性所提供的值仅适用于其子元素,而不适用于元素本身。
perspective的伴随属性是perspective-origin。该属性设置在 3D 空间中查看元素的点。以下是语法:
E { perspective-origin: x-position y-position; }
*x-position*值可以是left、right或center中的任何一个,*y-position*值可以是top、bottom或center。也可以使用百分比或长度值。你应该熟悉这些值对,尤其是在使用其他属性时,例如background-position或transform-origin。
默认值是center center(或50% 50%),这意味着你正在从元素的绝对中心开始查看透视线。改变perspective-origin的值会改变透视线的起点。
这可能听起来有点让人费解;不过,再次强调,展示比解释更容易。在接下来的例子中,我从不同的视角原点展示相同的变换元素。以下是代码:
.d3-parent { perspective: 200px; }
.d3-child { transform: rotateX(45deg) rotateZ(90deg); }
➊ .trans-1 { perspective-origin: left center; }
➋ .trans-2 { perspective-origin: 100% 100%; }
➌ .trans-3 { perspective-origin: 75% 25%; }
这些示例在图 13-8 中有说明。首先展示一个参考示例(最左侧);这是一个从center center默认原点查看的变换元素。第二个示例(➊)是相同的元素,但其perspective-origin位于元素左侧的中心。你查看该元素的角度已经发生变化,视点似乎位于元素的左侧。第三个示例(➋)使用了100% 100%的长度值,这将原点更改为右侧底部。视点似乎位于元素的右侧,并且是向上看的。最后一个示例(最右侧)(➌)则是从x轴 75%的位置和y轴 25%的位置进行查看;这个示例与参考示例相似,但视点略微偏移,位于元素的右侧且稍微高于元素。

图 13-8:不同的*perspective-origin*属性值
变换原点
在第十二章解释 2D 变换时,我谈到了每个元素的原点——所有变换应用的点——以及如何使用transform-origin属性设置其位置。这个相同的属性也用于设置 3D 变换的原点,但由于三维坐标系有三个轴(x-轴,y-轴和z-轴),因此该属性还接受三个值:
E { transform-origin: x y z; }
前两个值,*x* 和 *y*,与 2D 元素的transform-origin属性相同;也就是说,它们接受关键字(left,right,center用于*x*,top,bottom,center用于*y*)、长度单位或百分比作为值。默认值为center center,或50% 50%。第三个值,*z*,是一个长度值,用来设置变换发生的z-轴上的距离。这个值看起来可能有些反直觉,因为它似乎是倒转的;如果给定一个负值,变换原点就会位于元素的背后,这使得元素出现在其父元素的前面;同样,正值则将原点放置在元素的前面,使得元素出现在其父元素的后面。
我将通过一个演示来说明这一点,演示中有三个元素,除了transform-origin值外,其它所有值都相同。以下是这些示例的相关代码:
.d3-child { transform: rotateX(45deg) rotateZ(90deg); }
➊ .trans-1 { transform-origin: 50% 0 0; }
➋ .trans-2 { transform-origin: 50% 100% 0; }
➌ .trans-3 { transform-origin: center bottom -50px; }
你可以在图 13-9 中看到输出。从左到右,第一个例子再次是一个参考元素,原点位于其默认位置,即正中心。接下来的例子(➊)展示了相同的元素,transform-origin值设置为顶部中心,并且原始位置(未改变)的z-轴上。第三个例子(➋)将变换原点设置为元素的底部中心,而最后一个(最右侧)例子(➌)具有相同的变换原点(在x-轴和y-轴上使用关键字而不是百分比设置),但是在z-轴上是 50px——也就是说,朝向观察者(如图所示)。

图 13-9:变换元素上不同的*transform-origin*值
如前所述,如果你查看示例文件,你将能更清楚地看到这些差异,因为你可以看到元素实际上围绕其变换原点旋转。
transform-style 属性
当 3D 变换的元素嵌套时,默认行为是所有子元素都会被压平到父元素的平面上——也就是说,任何应用于子元素的变换函数都会被忽略。你可以通过一个名为transform-style的属性来更改这一行为;下面是语法:
E { transform-style: keyword; }
关键字值可以是flat(默认值)或preserve-3d。如果从一个例子开始解释这个差异会更容易:在这里,我有两个元素应用了相同的规则,唯一的区别是给transform-style属性赋的值:
.trans-1 { transform-style: flat; }
.trans-2 { transform-style: preserve-3d; }
如图 13-10 所示,差异非常明显:左侧示例使用了默认值 flat,因此内部元素与父元素保持在同一维度平面上;你可以清楚地看到与右侧第二个示例的对比,后者的 transform-style 值为 preserve-3d,内部元素位于一个独立的平面上,并且在父元素的 z 轴方向上进一步延伸。
警告 Internet Explorer 10 和 11 不支持此属性的 *preserve-3d* 值。

图 13-10:比较不同 *transform-style* 属性值的效果
显示或隐藏背面
有时你会遇到这种情况,元素已被旋转到一定角度,背对着你,你看到的是它的“背面”(称为 backface)。默认情况下,元素表现得像是透明的,因此你会看到前面显示的反面内容。你可以通过使用 backface-visibility 属性来改变这种行为,其语法如下:
E { backface-visibility: state; }
*state* 值是两个关键字之一:hidden 或 visible。默认值是 visible,它的行为如我刚才所描述;而 hidden 显示的是空白。它们的工作方式与 visibility 属性相同,你应该对这个属性在 CSS2 中有所了解。
以下是一个简短示例,通过展示两个元素来说明这两个 *state* 之间的差异,除了它们的 backface-visibility 值不同。代码如下:
.d3-child {
backface-visibility: visible;
transform: rotateY(180deg);
}
.bf-hidden { backface-visibility: hidden; }
你可以在图 13-11 中看到结果。两个示例元素围绕 y 轴旋转,背对你。左侧的示例中,元素的 backface-visibility 属性值为 visible,因此你可以清楚地看到元素的背面。在右侧示例中,你什么也看不见。backface-visibility 属性值为 hidden,因此什么都不显示——没有 border,没有 background-color,什么都没有。为了确认元素确实存在,你可以访问本书附带的网站,查看动画代码示例,这样你就能更清楚地看到它们是如何工作的。

图 13-11:演示 *backface-visibility* 属性
总结
第三维度的引入使 CSS 进入了一个充满潜力的领域——有许多演示展示了巧妙使用 3D 转换来构建物体或环境的示例。但对我而言,额外维度的最佳用途是在较小的、微妙的细节中;为鼠标悬停效果添加深度,或者制作可以翻转显示反面信息的双面“卡片”。
和所有事物一样,CSS 3D 转换最好适当使用,这样它们可以提升网站的用户体验,而不是通过不必要的技巧让体验变差。
3D 转换:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 3D 变换 | 是 | 是 | 是^(*) | IE10^(†) |
- 带供应商前缀
† 不支持 transform-style 的 preserve-3d 值
第十四章:14
过渡与动画(TRANSITIONS AND ANIMATIONS)

我们通常认为网页有三个层次:内容(HTML)、展示(CSS)和行为(JavaScript),通常理解是这三层应当完全分开——也就是说,不要在内容层使用展示或行为规则(换句话说,HTML 标记中不能有内联 CSS 或 JavaScript)。然而,这种分离并不像看起来那么明确——首先,CSS 一直包含一些行为方面的内容(例如,:hover伪类就是一个典型的例子)。
当 WebKit 开发者引入了两个新的行为模块并将其作为 CSS3 组件采纳时,展示和行为之间的界限变得更加模糊了:过渡和动画。这些模块允许对元素的属性进行动画处理,为原本静态的页面增添了动态效果,即使在没有 JavaScript 的情况下也能实现。
有些人曾经争论过——也许现在仍然在争论——过渡和动画模块是否应当纳入 CSS,因为它们完全属于行为层。但由于 W3C 决定将它们作为 CSS3 的“官方”部分进行讨论,我们不再需要辩论它们的伦理问题——我们可以直接享受它们带来的乐趣了!
过渡(transitions)和动画(animations)之间的区别在于,前者是隐式的,而后者是显式声明的。这意味着过渡只有在应用的属性值发生变化时才会生效,而动画则是在应用于元素时显式执行。
我将从过渡模块开始讲解,因为它是两个模块中更简单的一个;然而,这两个模块有许多相似的语法,因此从一个模块学到的很多内容可以直接应用到另一个模块上。
过渡(Transitions)
CSS2.1 没有中间状态:当属性值变化时,变化是突然发生的。考虑一个宽度为 10em 的元素,当你将鼠标悬停在其上时,宽度变为 20em。你会注意到元素在这两种状态之间并没有平滑过渡,而是直接跳跃过去。CSS3 通过引入过渡模块(www.w3.org/TR/css3-transitions/)改变了这一行为。在 CSS 中,过渡是一种将属性在两种状态之间平滑过渡的动画效果。
正如我在本章开头提到的,过渡是一种隐式动画,这意味着它们只有在为 CSS 属性设置新值时才会触发——这可以是在鼠标悬停时应用新值或通过 JavaScript 进行操作时触发。为了使过渡发生,必须满足四个条件:初始值、结束值、过渡本身以及触发条件。
这里是一个简单过渡中那四个条件的示例(暂时不用担心我使用的属性;稍后我会逐一解释):
div {
background-color: black;
transition: background-color 2s;
}
div:hover { background-color: silver; }
div 元素提供了初始值(background-color: black)和过渡效果(background-color 2s)。触发条件是 :hover 伪类,它为 background-color 属性设置了结束值(silver)。
所以这里有一个 div 元素,初始背景为黑色,当鼠标悬停时,它的背景平滑过渡为银色。当触发条件不再活跃时,所有的过渡效果都会反向执行,因此当鼠标移开 div 时,背景将平滑过渡回黑色。
现在你已经大致了解了过渡效果如何工作,我将依次介绍每个过渡属性。
注意
过渡属性在所有现代浏览器中都已实现,包括移动设备浏览器,无需厂商前缀。然而,为了兼容较老版本的基于 WebKit 的浏览器——特别是 Safari 和 Android 4.4 之前的版本——你应当在规则中添加带有 *-webkit-* 前缀的复制版本。
过渡属性
第一个新属性,transition-property,指定了元素的哪个属性(或哪些属性)将会被动画化(这是我在一句话中提到的最多次的 属性)。以下是其语法:
E { transition-property: keyword; }
一个可接受的关键字值是 all、none 或有效的 CSS 属性。默认值是 all,这意味着所有有效的属性都会被动画化。我强调 有效 的 CSS 属性,因为并不是所有属性都可以进行过渡;规范中列出了可以过渡的属性,详见 www.w3.org/TR/css3-transitions/#properties-from-css-/。
这是一个 transition-property 的示例:
h1 {
font-size: 150%;
transition-property: font-size;
}
这段代码为 font-size 属性设置了初始值 150%,并声明该属性将在触发条件(尚未指定)激活时发生过渡。请注意,在本节剩余部分我将逐步添加更多的属性,在 “完整过渡示例” 中展示完整的例子,见 第 173 页。
过渡持续时间
下一个属性是 transition-duration,它定义了过渡完成所需的时间。以下是其语法:
E { transition-duration: time; }
*时间* 值是一个带有单位 ms(毫秒)或 s(秒)的数字。因为 1000 毫秒等于 1 秒,所以 1.25s 等同于 1250ms。默认值是 0(零),意味着这是创建过渡所需的唯一属性。如果你声明了 transition-duration 而没有声明 transition-property(因为默认值是 all,因此所有有效属性都会被动画化),则过渡仍然可以发生,但反之则不行。
为了使前一节中的示例过渡在两秒钟内完成,你需要添加以下代码:
h1 {
font-size: 150%;
transition-property: font-size;
transition-duration: 2s;
}
尽管你可以为此属性提供负值,但它们会被解释为默认值 0。
过渡计时函数
为了控制元素在状态之间过渡的方式,你可以使用transition-timing-function属性。这个属性允许你在过渡的持续时间内调整速度,从而控制动画的节奏。该属性有三种不同的值类型:关键字、cubic-bezier()函数或者steps()函数。我会详细讨论这两个函数,但首先,我将重点讲解关键字。
时间函数关键字
当使用关键字时,transition-timing-function属性的语法相当简单明了:
E { transition-timing-function: keyword; }
可用的关键字值有ease、linear、ease-in、ease-out和ease-in-out。默认值是ease,它开始时较慢,加速较快,最后再慢下来。linear值则是从过渡开始到结束的过程中,速度均匀,没有变化。ease-in值使动画开始时较慢,接着加速,直到结束,而ease-out值则正好相反。最后,ease-in-out从开始时较慢,经过中间加速,再到结束时减速,类似于——但比ease值不那么戏剧性的——ease值。
解释完这一点后,让我们为示例过渡添加一个简单的时间函数:
h1 {
font-size: 150%;
transition-property: font-size;
transition-duration: 2s;
transition-timing-function: ease-out;
}
三次贝塞尔曲线
如果你想对transition-timing-function属性进行更精细的控制,应该使用cubic-bezier()函数。如果你对三次贝塞尔曲线不太熟悉——其实,为什么会熟悉呢?——让我来为你解释。首先,这里是语法:
E { transition-timing-function: cubic-bezier(x1, y1, x2, y2); }
三次贝塞尔曲线是一条平滑的连续曲线,它穿过四个点,这些点在一个从 0 到 1 的网格上绘制,横纵坐标轴都是从 0 到 1。四个点分别叫做P[0]、P[1]、P[2]和P[3]。它们定义了曲线的弯曲度,并通过一对对的(x, y)坐标绘制,其中第一个点(P[0])总是在(0, 0),最后一个点(P[3])总是在(1, 1)。另外两个点在函数中定义:(x1, y1) 和 (x2, y2)。一个例子,如图 14-1 所示,能最好地说明这一点。

图 14-1:三次贝塞尔曲线的示例
图 14-1 展示了这四个点如何映射到网格上,从而绘制出贝塞尔曲线。每个点的坐标列在表 14-1 中。
表 14-1:用于绘制贝塞尔曲线的坐标点
| 点 | 坐标 (x, y) |
|---|---|
| P[0] | (0, 0) |
| P[1] | (0.6, 0.1) |
| P[2] | (0.15, 0.7) |
| P[3] | (1.0, 1.0) |
你可以使用以下 CSS 来表示这条曲线(记住,你不需要定义P[0]和P[3],因为它们的值始终是固定的):
E { transition-timing-function: cubic-bezier(0.6, 0.1, 0.15, 0.7); }
线性动画从 (0, 0) 到 (1, 1) 按直线进展,但这个示例动画是在设定的持续时间内,遵循曲线的进展,直到达到最终点。如果你想象持续时间为 1 秒,你可以看到开始时速度逐渐增加,在 0 到(大约)0.5 秒之间,然后在 0.7 秒左右急剧加速,接着直到动画结束时,速度逐渐变慢。
之前描述的所有 transition-timing-function 关键字都是通过立方贝塞尔曲线生成的。表 14-2 显示了每个关键字及其对应的 cubic-bezier() 函数值。
表 14-2: 比较 transition-timing-function 关键字与其对应的立方贝塞尔值
| 关键字值 | 立方贝塞尔值 |
|---|---|
ease |
0.25, 0.1, 0.25, 1 |
linear |
0, 0, 1, 1 |
ease-in |
0.42, 0, 1, 1 |
ease-out |
0, 0, 0.58, 1 |
ease-in-out |
0.42, 0, 0.58, 1 |
就像我在第十三章中介绍的变换矩阵一样,cubic-bezier() 函数如果你不习惯数学,可能会让人感觉有些棘手。但别担心——你总是可以使用关键字值,这在大多数情况下已经足够了。
一个使立方贝塞尔曲线稍微容易理解的好工具是 Lea Verou 的 cubic-bezier.com/。这个工具允许你使用可视化界面创建自己的曲线,将其与关键字别名实时比较,并将它们导出为 cubic-bezier() 函数——这是理解曲线的一个非常有用的辅助工具。
steps() 函数
steps() 函数是平滑过渡的替代方案,它通过一系列错开的间隔来运行动画。该函数的语法如下所示:
E { transition-timing-function: steps(count, direction); }
*count* 值是一个整数,表示动画应该运行的间隔次数,且可选的 *direction* 是两个关键字之一——start 或 end(默认为 end),它设置每个间隔中变化发生的点。现在这可能还不太容易理解,但请耐心等待。
让我们来看一个 steps() 如何工作的简单例子。看看下面的规则,其中 steps() 函数的步数参数为 4,并通过省略可选的方向关键字,使用该函数的简单形式:
E { transition-timing-function: steps(4); }
在用于 cubic-bezier() 函数的定时功能网格上,它看起来像 图 14-2。因此,steps 并不像单一的过渡线,而是像是在看到动画的每一帧快照。

图 14-2:四个步进间隔的过渡
当动画以步进方式显示时,使用 *direction* 关键字来选择每个步进变化发生的时机:默认的 end 关键字表示变化发生在步进的结束时(暂停,然后变化),而替代的 start 则表示变化发生在步进的开始时(变化,然后暂停)。
这个过程在时间函数网格上也更容易可视化;在下面的代码中,同样的步骤数显示了不同的方向关键字:
E { transition-timing-function: steps(4, start); }
E { transition-timing-function: steps(4, end); }
你可以在图 14-3 中看到它们之间的区别,如果可以的话,我强烈建议你查看随附的示例文件(可以从 thebookofcss3.com/ 获得)——看到这两者的实际效果是理解区别的最佳方式。

图 14-3:比较方向关键字: *start* (左边) 和 *end* (右边)
transition-delay
transition-* 系列中的最后一个属性是 transition-delay,它设置过渡开始的时间。以下是语法:
E { transition-delay: time; }
和 transition-duration 一样,*time* 值是一个带单位的数字,单位可以是毫秒(ms)或秒(s)。默认值为 0(零),意味着过渡会在触发时立即发生。任何其他正值则会在指定时间过后开始过渡。
例如,如果你想在示例过渡的开始处设置一个四分之一秒的延迟,以下是你会使用的代码:
h1 {
font-size: 150%;
transition-property: font-size;
transition-duration: 2s;
transition-timing-function: ease-out;
transition-delay: 250ms;
}
你还可以为 transition-delay 使用负值,这会产生一个有趣的效果:过渡立即开始,但会跳过负值的时间量。为了说明我的意思,我们考虑一个持续时间为 4s 的过渡,但延迟为 -2s:
E {
transition-duration: 4s;
transition-delay: -2s;
}
当触发时,过渡立即开始,但就像两秒钟已经过去一样(两秒钟为持续时间减去延迟)。在这种情况下,动画会从过渡的中途开始。
过渡简写属性
在这一部分中,我通过一个属性一个属性地构建了一个示例过渡。目前,代码看起来是这样的:
h1 {
transition-property: font-size;
transition-duration: 2s;
transition-timing-function: ease-out;
transition-delay: 250ms;
}
这段代码似乎每个过渡都需要写很多。但和所有其他属于同一“系列”的 CSS 属性一样(例如background-*、border-*等等),transition-*系列也有简写方式。以下是语法:
E { transition: property duration timing-function delay; }
这里有一个重要的注意事项,那就是有两个时间值:transition-duration 和 transition-delay,它们必须按此顺序声明。如果只声明了其中一个,语法会假定它是 transition-duration,而 transition-delay 会被设置为默认值(或继承值)。
如果你使用来自示例过渡的值并应用简写属性,结果如下:
h1 { transition: font-size 2s ease-out 250ms; }
很明显,编写的代码少了很多。
完整的过渡示例
现在我已经解释了所有组件属性,让我们看看实际的示例过渡效果。完整的代码如下:
h1 {
font-size: 150%;
transition: font-size 2s ease-out 250ms;
}
h1:hover { font-size: 600%; }
图 14-4 显示了当我将鼠标移到 h1 元素上时发生的情况。

图 14-4:*font-size* 属性上动画过渡的三个阶段
显然,我无法在打印页面上展示完整的动画,但插图展示了过渡的三个阶段:初始的过渡前阶段(左侧),字体大小为 150%;中间的过渡中阶段(中间),动画进行到不到两秒时字体大小已增大;以及最后的过渡后阶段(右侧),字体大小为 600%。
如我之前所提到的,当触发条件不再满足时,过渡会反向执行,因此当你把鼠标从 h1 元素上移开时,可以从右到左阅读此示例,查看会发生什么。
多个过渡
您可以通过为单个或缩写属性提供逗号分隔的值列表,轻松地为一个元素添加多个过渡。这样的话,以下两个代码示例都是有效的:
E {
transition-property: border-width, height, padding;
transition-duration: 4s, 500ms, 4s;
}
E { transition: border-width 4s, height 500ms, padding 4s; }
请注意,如果某个属性的值比其他属性少,那么该值列表会被循环使用。考虑到这一点,您可以稍微修改这个代码示例:
E {
transition-property: border-width, height, padding;
transition-duration: 4s, 500ms;
}
在这里,transition-property 有三个值,而 transition-duration 只有两个值。这意味着前者的第三个值(padding)与后者的第一个值(4s)匹配,符合第一个示例中提供的内容。
这是一个实际示例:
.widget {
background-color: black;
left: 10%;
top: 60%;
transition: background-color 4s linear, left 2s ease-in-out, top 2s ease-in-out;
}
div:hover .widget {
background-color: silver;
left: 75%;
top: 10%;
}
在这里,我使用了 transition 缩写来应用三个过渡。第一个过渡将 background-color 从 black 变为 silver,采用 linear 时间函数,接下来的两个过渡分别使用 ease-in-out 时间函数改变 left 和 top 属性。background-color 过渡的持续时间为四秒,其他两个过渡则是两秒。
图 14-5 展示了过渡的三个阶段:第一阶段(左侧)显示元素在过渡前,背景为黑色,位于父元素的左下角;接下来的阶段(中间)是过渡中的阶段,元素正在改变颜色并移动到父元素的右上角;最后的阶段(右侧)显示元素在过渡后,背景为银色,并处于最终位置。

图 14-5:关于 *background-color*、*left* 和 *top* 属性的动画过渡三个阶段
动画
过渡效果很好,但自然是有限的;它们仅在属性值发生变化时应用。CSS3 动画模块 (www.w3.org/TR/css3-animations/) 超越了过渡的可能性,允许直接对元素应用动画,语法更加灵活,并且提供了更精细的控制。动画和过渡有很多相似的语法,但创建动画的过程非常不同:首先,定义属性和时间设置,然后将动画控制应用于将要动画化的元素。
注意
CSS 动画在 IE10 及以上版本和所有其他现代浏览器中得到实现,但截至撰写时,你仍然需要为 Chrome 和 Safari(包括桌面和移动端)使用*-webkit-*供应商前缀。
关键帧
你可以把 CSS 动画看作是一系列过渡,串联成一个序列。创建你自己的动画的第一步是定义你的关键帧,这些关键帧是设置过渡的开始和结束的节点。最简单的动画有两个关键帧——一个在开始,一个在结束——而更复杂的动画则有多个中间关键帧。图 14-6 展示了一个包含三个关键帧的动画效果。
在 CSS 中,关键帧是在@keyframes规则中声明的,其语法如下:
@keyframes name {
selector { property : value; }
}

图 14-6:一个包含三个关键帧的动画及其之间的过渡
注意
记住,你还需要重复你的关键帧规则集,以适应基于 WebKit 的浏览器。使用 WebKit 前缀: *@-webkit-keyframes*。
@keyframes规则的第一个值是*name*;这个唯一的标识符用于调用动画,我将在后面讨论。你可以在这里使用几乎任何值(如果你想要一个包含多个单词的名称,使用连字符分隔而不是空格),但我建议使用与动画描述内容相关的单词或术语——如果这样做,你的样式表会更容易理解。
下一个值*selector*设置了关键帧发生的动画时间点。通常这里使用百分比值;例如,如果你希望关键帧发生在动画的中间,可以使用50%。你也可以使用关键字from或to,它们分别相当于 0%和 100%。
在每个关键帧选择器中,都有一个 CSS 声明或一系列声明,这些声明会在动画的指定阶段应用于选定的元素。如果这让你感到困惑,让我通过一个例子来解释。以下代码描述了一个简单的动画,我将其命名为expand,并包含三个关键帧:
@keyframes expand {
➊ from { border-width: 4px; }
➋ 50% { border-width: 12px; }
➌ to {
➍ border-width: 4px;
height: 100%;
width: 100%;
}
}
在动画开始时(➊),选定的元素有一个宽度为4px的边框;在动画进行到一半时(➋),边框的宽度增加到12px;在动画结束时(➌),边框宽度恢复为4px,并且高度和宽度都为100%。在每个关键帧之间,元素会逐渐过渡,所以在开始和中途的动画之间,边框的宽度会平滑地从4px变化为12px。
请注意,继承在单个关键帧上起作用,因此如果你希望某个变化在帧之间持续存在,你需要在每一帧中都指定它。如果我没有在to关键帧(➍)中再次指定border-width,它将默认为应用于动画的元素的继承值,这样动画的效果就会有所不同。
关键帧选择器可以像其他 CSS 选择器一样链式使用,因此我可以像这样编写之前的代码示例:
@keyframes expand {
from, to { border-width: 4px; }
50% { border-width: 12px; }
to {
height: 100%;
width: 100%;
}
}
同样,你并不需要按时间顺序列出关键帧选择器;将to放在from之前也是完全可以接受的(尽管我敢打赌这样会更难维护),任何声明冲突都通过使用级联来解决:后声明的规则优先。例如,看看以下关键帧规则集,其中两个关键帧在同一时间点定义:
@keyframes example {
10% { background-color: red; }
10% { background: green; }
}
当动画应用时,元素的背景色将在10%的时刻变为green,因为后面声明的规则将会生效。
一旦你定义了关键帧,下一步就是将动画控制属性应用到你想要动画化的元素上。正如我在本节的介绍中提到的,许多动画属性与transition-*系列中的相应属性共享相同的语法,因此你应该已经非常熟悉它们了。
animation-name
animation-name属性指的是通过@keyframes规则定义的动画,因此,语法相当简单:
E { animation-name: name; }
你可以看到,它只需要一个值,这个值是已经定义好的动画的名称。要调用在上一节中创建的动画,你可以使用以下代码:
div { animation-name: expand; }
唯一允许的其他值(也是默认值)是none,它会阻止该元素上发生任何动画。
animation-duration
动画的持续时间由animation-duration属性设置,它的功能与本章前面介绍的transition-duration属性完全相同:
E { animation-duration: time; }
和transition-duration一样,*time*值是一个带单位的数字,可以是 ms 或 s,或者是0(零),它会阻止动画运行。(负值也会被实现为0。)为了将示例动画的持续时间延长为六秒,你需要添加这一行:
div { animation-duration: 6s; }
animation-timing-function
另一个应该很熟悉的属性是animation-timing-function,它的功能上与对应的transition-timing-function属性完全相同:
E { animation-timing-function: value; }
允许的值包括计时函数关键字(ease,linear,ease-in,ease-out和ease-in-out),cubic-bezier()函数(在第 168 页的“Cubic Bézier 曲线”中解释)或steps()函数(在第 170 页的“steps()函数”中解释)。因此,如果你希望动画在开始时“缓入”,你可以添加以下代码:
div { animation-timing-function: ease-in; }
animation-delay
另一个熟悉的属性是animation-delay,它的功能与其对应的transition-delay属性完全相同:
E { animation-delay: time; }
和 animation-duration(以及两个对应的 transition-* 属性)一样,时间值是一个带有单位的数字,可以是 ms 或 s,它设置动画开始之前的延迟时间。0(零)意味着没有延迟。与 transition-duration 类似,负值会导致动画按该值“跳过”。
要让示例动画延迟两秒钟,可以包含以下代码:
div { animation-delay: 2s; }
animation-iteration-count
与只会发生一次的过渡不同(如果算上反向,可能会有两次),动画可以重复任意次数。重复次数由 animation-iteration-count 属性设置,语法如下:
E { animation-iteration-count: count; }
该语法中的 *count* 值可以是一个整数或者关键字 infinite。数字值设置动画重复的次数。默认值是 1(一次),意味着动画从头到尾播放一次,然后停止。infinite 值意味着动画无限循环,或者至少直到满足另一个条件来改变该值。0(零)或任何负数会阻止动画播放。
要继续我在本节中构建的示例,如果我想让动画重复 10 次,我需要添加以下声明:
div { animation-iteration-count: 10; }
animation-direction
动画从开始播放到结束,但它也可以反向播放。你可以设置动画是否始终在一个方向播放,或者交替正向和反向播放。要做到这一点,可以使用 animation-direction 属性:
E { animation-direction: keyword; }
关键字值有两个选项:normal 或 alternate。默认值是 normal,表示动画始终向前播放:动画从开始到结束播放,然后,如果设置为重复,它将从头开始重新播放。如果使用 alternate 值,动画先正向播放,再反向播放,然后重新开始。如果将每次动画的循环视为一个“周期”,奇数周期正向播放,偶数周期反向播放。你可以在 图 14-7 中看到这种差异。
为了完成示例动画,让我们设置动画为 alternate 正向和反向播放:
div { animation-direction: alternate; }

图 14-7:一个以 *normal* 方向播放的动画从头到尾重复;使用 *alternate* 播放时,每个第二个周期从结束到开始播放。
animation-fill-mode
如果你的动画是有限的——也就是说,如果它的 animation-iteration-count 属性没有设置为无限值——那么你可以使用 animation-fill-mode 属性来设置元素在动画周期之外的显示方式。为了向你展示我的意思,让我们来看以下样式规则:
@keyframes example {
from { background-color: red; }
to { background-color: blue; }
}
E {
animation-delay: 1s;
animation-duration: 1s;
animation-iteration-count: 2;
animation-name: example;
background-color: green;
}
在这个规则中,元素 *E* 在动画触发之前延迟 1s,然后立即变为 red,接着过渡到 blue,重复两次(2),最后恢复到初始的 green。
你可以使用animation-fill-mode来更改这种行为。语法如下所示:
E { animation-fill-mode: keyword; }
允许的关键字有none(默认值)、backwards、forwards或both。如果使用backwards,在动画开始之前,0%(或from)关键帧中指定的声明将应用于元素;如果使用forwards,在动画结束时,100%(或to)关键帧中的声明将应用;如果使用both,0%的声明将在动画之前应用,100%的声明将在动画之后应用。
所以回到之前的示例代码,如果你添加以下规则:
E { animation-fill-mode: both ; }
然后元素*E*将从red开始,过渡到blue,并在动画结束时保持这个颜色;它原本的green颜色在任何时候都不会被应用。
animation-play-state
animation-play-state属性设置动画是否处于活动状态。语法如下:
E { animation-play-state: keyword; }
关键字值有两个选项:running表示动画正在播放,paused表示动画没有播放。你可以使用这个属性来执行播放/暂停操作:
E:hover { animation-play-state: paused; }
在这个示例中,如果假设元素*E*应用了动画,鼠标悬停在元素上时,动画会暂停,直到鼠标移开,动画才会继续。
动画简写
在本节中,我一次一个属性地组装了一个动画示例。以下是所有属性结合在一起后的效果:
div {
animation-name: expand;
animation-duration: 6s;
animation-timing-function: ease-in;
animation-delay: 2s;
animation-iteration-count: 10;
animation-direction: alternate;
animation-fill-mode: forwards;
animation-play-state: running;
}
对于每个动画来说,声明这么多属性有点繁琐。然而,你可以利用一个叫做animation的简写属性,语法如下:
E { animation: name duration timing-function delay iteration-count direction
fill-mode play-state; }
和transition简写一样,animation-duration和animation-delay必须按顺序指定。如果漏掉其中一个,语法默认认为是animation-delay,并给它一个默认值0。
为了使我在本章中构建的示例更加简洁,我可以使用这段简写代码:
div { animation: expand 6s ease-in 2s 10 alternate both running; }
这看起来可能仍然比较冗长,但在最简单的情况下,animation属性只需要两个值:关键帧的名称和持续时间:
div { animation: expand 6s; }
完整的动画示例
解释了所有属性之后,让我们来看一下完整的动画示例实际效果。以下是 CSS 代码(为了简洁起见,部分规则已被省略;完整代码请参见示例文件):
@keyframes expand {
➊ 0% { border-width: 4px; }
➋ 50% { border-width: 12px; }
➌ 100% {
border-width: 4px;
height: 100%;
width: 100%;
}
}
div {
…
animation: expand 6s ease 0 infinite alternate;
}
不幸的是,我无法在这本书中展示动画,所以我将用文字描述它。图 14-8 展示了元素在动画的三个关键帧状态:0%(➊)关键帧显示元素的border-width为4px,并且继承了height和width(每个是100px,这里未显示);在50%关键帧(➋)中,border-width增加到12px;在最终的100%关键帧(➌)中,border-width恢复为4px,height和width都变化为100%。

图 14-8:影响*border-width*、*height*和*width*属性的动画的三个阶段
因为我的 animation-direction 值是 alternate,所以动画会反向播放,缩回到原始尺寸,然后由于 animation-iteration-count 的值,它会继续无限地增长和缩小。
多重动画
你可以通过使用逗号分隔的列表向一个元素添加多个动画。这种方法适用于每个子属性和简写属性,因此这两个例子都是有效的:
E {
animation-name: first-anim, second-anim;
animation-duration: 6s, 1.25ms;
animation-delay: 0, 750ms;
}
E { animation: first-anim 6s, second-anim 1.25ms 750ms; }
在这里,通过循环值列表来确保所有属性应用相同数量的值,方式与《多重过渡》一节中第 174 页所描述的完全一致。
总结
向 CSS 中添加动画最初被认为是一个有争议的举动,但我认为这些模块编写者使用的语法相当优雅,增加了许多灵活性而不显得过于复杂。
开发者曾担心过渡和动画会被滥用,导致我们看到大量华而不实、无法使用的网站。尽管这是一个真实的担忧,但现在已有的 CSS 属性其实也无法阻止人们这样做(实际上,许多人就是这么做的!)。不过,若能适度且恰当地使用,这些强大的新工具确实能够为网页增添活力。
过渡和动画:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 过渡 | 是 | 是 | 是 | IE10 |
| 动画 | 是^(*) | 是 | 是^(*) | IE10 |
- 带供应商前缀
第十五章:15
弹性盒布局

网页看起来如此美观,可以说是一种小小的奇迹(当然,相对而言!)。自从从基于表格的布局转向纯 CSS 布局以来,开发人员经常不得不使用简单的工具,如 float、margin 和 position,来创建复杂的页面结构——这些工具在最初设计时可能并非为了这个目的。随着 CSS 的成熟和浏览器功能的增强,提出了一系列新的替代布局方法。
最广泛实现的方法被称为弹性盒布局(或 Flexbox)。这种方法定义在弹性盒布局模块(www.w3.org/TR/css-flexbox-1/)中,它使得元素能够灵活调整大小,更好地适应可用空间,而无需使用浮动、定位或复杂的计算。
尽管你完全可以使用 Flexbox 创建整个页面布局,但它更适用于处理界面元素和较小的组件。在处理整个页面布局时,建议使用网格布局模块(Grid Layout Module),我将在第十七章中介绍它。但如果你正在构建用户界面或应用程序(尤其是包含许多按钮、表单元素或交互区域的应用),你会发现 Flexbox 非常有用。
声明弹性盒模型
使用 Flexbox 的第一步是创建弹性容器——这个父元素将为其内容创建一个新的格式化上下文。要声明一个弹性容器,你只需为 display 属性使用一个新值:
E { display: flex; }
这会创建一个块级弹性容器;如果你更喜欢内联级容器,可以使用 inline-flex 值。
现在,你可以将弹性项目添加到弹性容器中。弹性项目是弹性容器的任何子项,它会受到容器创建的格式化上下文的影响。例如,在以下代码中,如果 #container 被设置为弹性容器,那么这两个子项将成为弹性项目:
<div id="container">
<div id="a">…</div>
<div id="b">…</div>
</div>
这种标记和 CSS 的组合如图 15-1 所示。请注意,这两个元素的宽度相等并且并排排列,无需使用浮动或定位属性。默认情况下,弹性项目会按照文档文本的方向排列——即,对于英语等语言,从左到右排列;对于阿拉伯语等语言,从右到左排列(可以通过 dir HTML 属性或 direction CSS 属性来指定);对于日语等语言,从上到下排列(通过 text-direction CSS 属性设置,但尚未广泛支持)。

图 15-1:弹性容器的子项默认水平排列。
注意
本章其余部分的所有示例都使用从左到右的方向,除非另有说明。
要更改默认的布局方向,您可以在容器上使用flex-direction属性。默认值row会将项目排列成一行,而column值则会将项目从上到下排列成一列。
E {
display: flex;
flex-direction: column;
}
Flexbox 对齐方式
Flexbox 使用两条轴线进行对齐。如图 15-2 所示,主轴沿着项目排列的方向,横向或纵向排列。当flex-direction的值为row时,主轴是水平的;当它是column时,主轴是垂直的。交叉轴是与主轴垂直的线:当方向为row时,交叉轴是垂直的;当方向为column时,交叉轴是水平的。

图 15-2:主轴和交叉轴取决于 flex 子项的布局方向。
在处理 flex 容器和项目时,您经常会看到所谓的轴的起点和终点。因为 flex 轴可以被反转(从上到下或从下到上,从左到右或从右到左),所以使用起点和终点,而不是相对方向,以避免混淆。例如,当主轴是水平的且方向是从左到右时,主轴的起点是左侧,终点是右侧;但如果主轴是垂直的,则主轴的起点是在顶部,终点在底部(如果反转则相反)。
反转内容顺序
Flexbox 的一个强大功能是,您可以快速更改项目的显示顺序,而不管它们在 DOM 中的顺序。例如,图 15-1 显示了两个项目按它们在 DOM 中声明的顺序排列成一行。如果您想更改它们的顺序,让#b排在#a之前,该怎么办呢?
您可以通过flex-direction属性快速做到这一点,使用row-reverse值来反转 flex 项目的显示顺序,如图所示。(column-reverse属性值会反转垂直排列的 flex 项目的顺序。)
E { flex-direction: row-reverse; }
结果如图 15-3 所示。

图 15-3:*row-reverse*值快速反转 flex 项目的顺序。
由于像这样的方向反转也会反转轴的方向,因此在row-reverse的情况下,轴的起点在左侧,终点在右侧;在column-reverse的情况下,起点在底部,终点在顶部。
完全重新排列内容
你可以使用order属性创建自定义的排序模式。order属性应用于 flex 项目(而不是它们的容器)。该属性的值是一个数字,用于创建一个顺序组,该组将具有相同值的项目分组,并按顺序组的顺序排列它们:最低编号的顺序组中的所有项目排在最前面,然后是第二低编号顺序组的所有项目,依此类推。任何没有声明值的项目会首先显示,因为它们的默认值为 0。
拥有相同顺序组编号的项目按它们在 DOM 中出现的顺序进行分组。例如,考虑四个 flex 项目,#a到#d:
<div id="container">
<div id="a">…</div>
<div id="b">…</div>
<div id="c">…</div>
<div id="d">…</div>
</div>
如果没有设置显式的值,并且如果flex-direction没有被反转,那么子项将按照它们在 DOM 中出现的顺序显示:#a、#b、#c、#d。但是,接下来我们通过使用不同的order属性值来重新排列它们:
#a { order: 2; }
#b, #d { order: 3; }
#c { order: 1; }
应用这些规则后,项目的排列顺序变为:#c、#a、#b、#d。项目#c排在最前面,因为它属于最低编号的顺序组,接着是编号次低的#a,然后是#b和#d——这两者都属于顺序组 3。项目#d排在最后,因为它在 DOM 顺序中排得较晚。
图 15-4 显示了结果。

图 15-4:使用顺序组重新排列的 flex 项目
注意,#c和#a共享相同的背景颜色,#b和#d也是如此。背景颜色应该交替显示,因为我使用了在第四章中讨论的:nth-child()伪类:
.flex-item:nth-child(even) { background-color: gray; }
但请记住,项目只是视觉上改变了顺序;它们在标记中的顺序保持不变,这就是为什么:nth-child()将灰色背景应用于应为偶数编号的元素——即#b和#d。
添加灵活性
使用 Flexbox 时,你几乎肯定会遇到这种情况:沿主轴的 flex 项目的总长度大于或小于 flex 容器的宽度。当这种情况发生时,Flexbox 的“灵活”特性就会发挥作用。几个属性允许 flex 项目根据需要增长或缩小以填充它们的容器。我将在本节中带你了解这些属性。
flex-grow属性
假设你有一个宽度为 600px 的 flex 容器,里面包含三个 flex 项目。每个项目的宽度是 150px,总宽度为 450px。容器的宽度和项目的总宽度之间的差异留出了 150px 的空白区域(即 600 − 450),如图 15-5 所示。

图 15-5:三个 flex 项目的总宽度小于其容器的宽度,导致右侧留有空白区域。
要扩展项目以填充容器,你可以使用flex-grow属性:
.flex-item { flex-grow: 1; }
flex-grow属性的值基本上是一个比率,用于在 flex 项目之间分配空余空间,以便它们扩展。在这个例子中,我使用了 1:1:1 的比例,将空余的 150px 均等地分配给三个 flex 项目。因为 150 除以 3 等于 50,每个项目扩展了 50px,使得它们的总宽度等于容器的宽度,如图 15-6 所示。

图 15-6:这三个 flex 项目现在填充了它们容器的宽度。
你也可以提供不同的值来调整分配比例。例如,若要使#b占据比其他两个项目更多的容器宽度,你可以为#b设置一个值为 3:
#b { flex-grow: 3; }
现在,150px 将按 1:3:1 的比例重新分配,因此对于每一个分配给#a和#c的像素,#b将分配到三个像素。结果是#a和#c的宽度各自扩展到 180px,而#b的宽度将变为 240px,如图 15-7 所示。

图 15-7:因为 *#b* *的*flex-grow* 值更高,当调整大小时,它比其他兄弟项更宽。
因为flex-grow的默认值是 0(零),所以 flex 项目会保持它们的宽度,并不会扩展来填充容器,除非明确指示这样做。
flex-shrink 属性
就像flex-grow用于扩展 flex 项目以填充容器一样,flex-shrink用于缩小项目。例如,让我们回到前一节的 flex 容器,包含#a、#b和#c项目;只不过这次我们将每个项目的宽度设为 300px。现在,三个项目的总宽度是 900px,超过了父容器 600px 的宽度,超出部分为 300px。
为了将这些元素缩小到适应容器的宽度,你可以使用flex-shrink属性:
.flex-item { flex-shrink: 1; }
flex-shrink属性的作用类似于flex-grow,但方向相反。例如,值为 1(默认值)时,每个项目的宽度会按相同比例缩小——每个项目缩小 100px(300 除以 3)。结果是每个项目的宽度为 200px,总宽度为 600px,与容器宽度一致。
像flex-grow一样,不同的值会改变分配比例。例如,如果你为项目#b使用值为 3,它的宽度将在每次其他两个项目缩小 1px 时,减少 3px。
#b { flex-shrink: 3; }
较高的数字会更大比例地缩小元素。在这个例子中,#a和#c的宽度每减少 1px,#b的宽度就会减少 3px。正如你在图 15-8 中看到的,#a和#c的宽度为 240px,而#b只有 120px,比它原本的宽度更窄。

图 15-8:Flex 项目 *#b* *的*flex-shrink* 值比它的兄弟项更高,因此它被按比例缩小以适应容器。
flex-basis 属性
Flex 项目的宽度可以通过它们包含的内容或明确的 width 值来设置,任何扩展或收缩都是基于这个基准宽度来计算的。要改变宽度调整的计算方式,可以在元素上设置 flex-basis 值。这个属性的值可以是一个长度单位。以下是一个示例:
.flex-item { flex-basis: 100px; }
当应用 flex-basis 时,任何现有的 width 值将被忽略,而你为 flex-basis 指定的值将用于计算调整。例如,在前两节中,150px 的 width 值被忽略,所有的扩展或收缩都是基于 flex-basis 值 100px 来进行的。你可以在 图 15-7 中将 #b 的 flex-basis 值设置为 100px,如下所示:
.child-item {
flex-grow: 1;
width: 150px;
}
#b {
flex-basis: 100px;
flex-grow: 3;
}
现在容器中的空白区域将按照 1:3:1 的比例重新分配,这意味着根据 width 值,#a 和 #c 各自会扩展 30px,而 #b 会扩展 90px。然而,由于 flex-basis 值的存在,这种空间分配的方式就像 #b 的宽度是 100px,而不是指定的 150px 宽度。
起初,似乎不合逻辑,尽管 #b 的 flex-basis 值设置得较小,最终它的宽度却大于兄弟项。原因是现在有 200px 的空闲宽度(flex 项目的总宽度为 400px,而父容器为 600px)。这 200px 将按 1:3:1 的比例在三个盒子之间重新分配。虽然 #b 起始时宽度为 100px,但它获得了 120px 的空余空间,而 #a 和 #c 各自获得了 40px。
结果是,#a 和 #c 最终宽度为 190px,而 #b 的宽度为 220px。将 图 15-9 中的结果与 图 15-7 进行比较,你可以看到它们之间的区别。

图 15-9: 因为 *#b* 应用了 *flex-basis* 值,它的尺寸被调整得比兄弟项更大。
Flex 简写属性
与许多属性一样,flex-* 系列也有一个简写属性,叫做 flex。它的值依次是 flex-grow、flex-shrink 和 flex-basis。考虑以下示例:
E { flex: 1 2 150px; }
在这里,元素 *E* 的 flex-grow 值为 1,flex-shrink 值为 2。使用的值是根据 flex 项目与它们的 flex 容器的宽度进行选择的;当 flex 项目没有填满容器时,使用 flex-grow 值,而当 flex 项目超出容器时,则使用 flex-shrink 值。最终的值 150px 是 flex-basis 值。
容器内的对齐
当你在一个 flex 容器中有固定尺寸的项目时,通常会在一个或两个轴上留下空白区域。例如,在 图 15-5 中,三个每个宽度为 150px 的 flex 项目并没有填满它们的 600px 容器。当出现这种情况时,你可以在容器内对项目进行对齐,以更好地利用可用空间。
水平对齐与 justify-content
幸运的是,Flexbox 提供了对对齐和定位的精确控制,允许你使用justify-content属性重新分配未使用的空间。此属性应用于弹性容器,并接受一系列关键字值,根据弹性父容器的方向(如行、列、反转行等)以不同方式应用:
.flex-container { justify-content: keyword; }
默认值是flex-start,这将所有弹性项对齐到父容器的左侧,未使用的空间占据剩余的右侧宽度。图 15-5 展示了这一效果。其他可选值包括:
• flex-end,将项目对齐到容器的右侧,未使用的空间位于左侧
• center,将未使用的空间分配到所有项目两侧,使项目居中
• space-between,在每个项目之间添加相等的空间,但第一个和最后一个项目之间没有空间
• space-around,在每个项目的两侧留有相等的空白
以下代码展示了一些不同的值供比较,结果见图 15-10。
.container-a { justify-content: flex-start; }
.container-b { justify-content: center; }
.container-c { justify-content: space-around; }

图 15-10:*justify-content*属性的不同值: *flex-start* (顶部), *center* (中间),和 *space-around* (底部)
使用 align-items 进行垂直对齐
当你的弹性项高度小于弹性容器的高度时,可以使用align-items属性来调整容器内的项目:
.flex-container { align-items: keyword; }
此属性的主要关键字值包括:
• stretch,使项目与父容器高度相同
• flex-start,将项目对齐到容器的顶部
• flex-end,将项目对齐到容器的底部
• center,将项目对齐到容器的垂直中心,上下方留有相等的空白
如果项目没有明确指定高度,则默认值为stretch,如果有则为flex-start。
在下面的代码示例中,我将弹性容器的flex-direction值设置为column,因此主轴是垂直的,交叉轴是水平的。图 15-11 展示了结果。
.container-a { align-items: stretch; }
.container-b { align-items: flex-end; }
.container-c { align-items: center; }

图 15-11:交叉轴对齐通过不同的*align-items*值进行控制: *stretch* (左), *flex-end* (中),和 *center* (右)。*
注意,在未设置明确宽度的情况下,第二列和第三列中的弹性项仅根据其内容的宽度来设置大小,然后根据容器的align-items值进行对齐。
使用 align-self 进行交叉轴对齐
要控制单个项目在交叉轴上的对齐方式,请使用align-self属性。此属性适用于项目,而不是容器。其值与align-items相同,且仅对选中的项目产生影响;兄弟项目不受影响。
例如,在以下代码中,元素#c的值与其兄弟元素不同:
.container { align-items: flex-end; }
#c { align-self: flex-start; }
效果(使用默认的行方向)如图 15-12 所示。

图 15-12:弹性项 *#c* 由于 *align-self* 属性的作用,与其兄弟元素的对齐方式不同。
换行与流动
当容器中的项太多,无法舒适地放入一行(或一列)时,你可以使用flex-wrap属性将它们分布到多行中。nowrap的默认值将所有项保持在同一行,而wrap的值则会在需要时将它们分到第一行下方的额外行中(或在列视图中向右分布)。
.flex-container { flex-wrap: wrap; }
wrap-reverse的值会改变交叉轴的方向,使新的行出现在上方(或左侧)。图 15-13 对比了这两个不同值的效果。

图 15-13:对比不同*flex-wrap*属性值的效果。顶部示例的值为*wrap*,因此元素*#c*出现在下方的新行中,而底部示例的值为*wrap-reverse*,因此元素*#c*出现在上方的新行中。
flex-flow 简写
你可以将flex-wrap与flex-direction结合,使用flex-flow属性的简写形式。例如,要设置一个带有多行并且交叉轴反转的列,可以使用以下代码:
E { flex-flow: column wrap-reverse; }
使用 align-content 对齐多行
当项目在多行中换行时,你可以通过align-content属性控制它们的对齐方式。此属性的作用类似于justify-content,但应用于交叉轴。它具有相同的可能值——flex-start、flex-end、center、space-between和space-around——并且还添加了stretch,它会调整项的大小以填充所有未使用的空间。图 15-14 对比了center和space-between值的效果。

图 15-14:当弹性项被换行时,交叉轴上的对齐由*align-content*控制:*center*(顶部)和*space-between*(底部)。
浏览器支持和遗留语法
在写这篇文章时,Flexbox 已经在所有主流现代浏览器中得到了很好的实现,但它有着长时间的语法变更历史,支持一些较旧的浏览器可能会比较麻烦。例如,Internet Explorer 10 实现了该规范的早期版本,所有属性都使用 -ms- 前缀。IE10 实现与现代规范有一些关键区别:IE10 使用 -ms-flexbox 作为 display 属性的值名称,而不是 flex;并且使用 -ms-flex-align 和 -ms-flex-pack,而不是 align-items 和 justify-content(它们的值也略有不同)。IE10 还没有实现 flex-wrap 或 flex-flow 简写。如果你需要支持 IE10,建议阅读关于该规范差异的 IE10 开发者指南 (msdn.microsoft.com/en-us/library/ie/hh673531%28v=vs.85%29.aspx)。
旧版本的 WebKit 核心浏览器,特别是 Safari 6 及以下版本,使用了更为过时的语法。如果你真的需要完全支持这些旧浏览器,可以查看 Stephen Hay 2009 年的介绍文章 (www.the-haystack.com/2010/01/23/css3-flexbox-part-1/).
但不要让这些问题阻止你使用 Flexbox。只有少数几个浏览器需要这些较旧的属性,只需稍加努力,你就能利用 Flexbox 带来的布局优势。
总结
尽管它为 CSS 引入了许多不熟悉的新术语和概念,但 Flexbox 是一个优雅且合乎逻辑的提案,解决了网站布局中几个常见的问题。Flexbox 比起写作,亲自实践学习要容易得多!
花时间完全理解 Flexbox 是非常值得的,因为它引入的许多新概念和关键字现在在其他布局模块中也至关重要,这使得它成为 Web 上丰富布局未来的核心。
Flexbox: 浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 弹性盒布局 | 是 | 是 | 是^(*) | IE10^(†), IE11 |
- 带有厂商前缀
† 支持旧的语法,并带有厂商前缀
第十六章:16
值和尺寸

CSS3 的许多新特性,从新的选择器到布局和字体属性,都是为了给你比 CSS2.1 提供更多的展示控制。这种控制将在本章中更加明显,我将介绍新的值函数和单位,以及自动计算尺寸的方法。
相对长度单位
在 CSS 中,相对长度单位 是指其值相对于其他属性的单位。CSS2.1 中的两个相对单位是 em,它是根据元素的 font-size 属性计算的,和 ex,它是根据元素字体的 x-height(参见 “font-size-adjust” 在 第 56 页)计算的。
CSS3 扩展了相对单位的范围,这些单位不再仅仅相对于元素的字体大小。新单位定义在“值和单位模块”(www.w3.org/TR/css-values/)中。
根相对单位
CSS3 中引入的第一个新单位是 rem,或称 根 em。它的行为类似于 CSS2.1 中的 em 单位,但不同的是,它不是相对于当前元素的 font-size 值,而是相对于文档根元素(html 元素)的 font-size 值。
尽管 em 很有用,但也不是没有缺点,尤其是在嵌套元素时,这些缺点会变得更加明显。为了说明这个问题,我将使用以下标记:
<ul>
<li>Western gorilla
<ul>
<li>Western lowland gorilla</li>
<li>Cross River gorilla</li>
</ul>
</li>
</ul>
以及这个简单的样式规则:
li { font-size: 2em; }
如果假设文档的根font-size是常见的浏览器默认值 16px,那么第一个 li 元素的计算font-size将是 32px(16 乘以 2)。但嵌套在第一个 li 内的 li 元素的 font-size 将相对于继承的值计算,使得它们的字体大小为 64px(32 乘以 2)。
这时,rem 单位变得至关重要。这里是与前一个示例相同的代码,只不过这次使用了 rem 替代了 em 单位:
li { font-size: 2rem; }
同样假设根 font-size 为 16px,第一个 li 的计算 font-size 为 32px。然而,这时嵌套的 li 元素的 font-size 也相对于根值计算,与它们的父元素相同。无论嵌套层级如何,这个值始终相对于根元素。
视口相对单位
在构建响应式设计时,开发人员往往使用百分比值来进行布局,因为它们可以在不同屏幕尺寸的范围内流畅缩放,以适应各种网站需求。百分比在顶层非常有用,但正如你刚才看到的,使用 em 单位时,在嵌套元素中使用百分比可能会遇到困难。
这段代码说明了问题:
<div class="parent">
<div class="child">…</div>
</div>
现在,假设 .parent 占视口宽度的 75%,你希望 .child 占视口宽度的 65%——而不是它父元素的宽度。要做到这一点,你需要将 65 除以 75,得到 86.666(百分比)。这个计算相对简单,但嵌套层次越深,计算就会变得越复杂。
一个更好的解决方案是使用 CSS3 的视口相对单位——vh 和 vw,它们分别表示视口的高度和宽度。每个单位的值代表适当视口维度的 1%:1vh 是视口高度的 1%,1vw 是视口宽度的 1%。例如,以下代码使元素占视口宽度的 75%,高度的 50%:
E {
height: 50vh;
width: 75vw;
}
使用这些单位的好处是,当元素嵌套时,这些单位仍然相对视口。因此,在我之前的例子中,要让 .child 占视口总宽度的 65%,只需这样做:
.child { width: 65vw; }
不需要计算!
还有一对补充单位可用:vmax 等于 vh 和 vw 中较大的一个值,vmin 等于较小的值。例如,如果视口为 480×640,视口高度较大,那么 vmax 等于 vh,而 vmin 等于 vw。如果交换视口的尺寸(640×480),那么 vmax 和 vmin 的值就会互换。
假设视口为 480×640,在以下代码片段中,元素*E*的宽度为 640px,元素*F*的宽度为 480px:
E { width: 100vmax; }
F { width: 100vmin; }
vmax 和 vmin 的作用是确保元素在任何方向下都能保持与视口的比例关系——当方向可能轻松变化时非常有用,比如在移动设备或平板上。
Internet Explorer 9 实现了 vmin 作为 vm 单位,但它和 IE10 都不支持 vmax(在 IE11 中才开始支持)。许多旧版智能手机浏览器不支持这些属性,尽管较新版本(如 iOS 6.0 和 Android 4.4 及以上版本)支持它们(尽管通常不支持 vmax,特别是在 iOS 中,截至目前为止)。
计算值
CSS3 的最大变化之一在于长度声明的方式。在 CSS2.1 中,长度总是一个数值加上一个单位,如果需要计算(例如,减去边框宽度以得到总宽度),开发者必须手动计算。但在 CSS3 中,浏览器会自动进行计算。
CSS 计算通过 calc() 函数进行。你可以在使用常见值单位(如长度、角度、数字等)的任何地方使用此函数。它的参数是使用这些常见值单位和四个基本运算符(+(加法)、-(减法)、*(乘法)和 /(除法))的任何数学表达式。
calc() 函数在混合单位时特别有用。例如,你可以创建一个表达式来计算元素的宽度(作为百分比),减去它的边框(作为 em 单位),像这样:
E {
border: 10px;
width: calc(75% - 2em);
}
加法和减法可以使用任何单位进行运算,但在进行乘法时,操作数两侧至少有一个参数必须是无单位的数字。对于除法,操作数之后的参数必须是无单位的数字。以下是如何进行乘法和除法的示例:
E {
left: calc(5 * 10em);
width: (80% / 4);
}
你可以在表达式中使用括号来显示计算顺序。例如,以下代码展示了一个进行三次计算的表达式:
E { height: calc(10% * 5 + 15% * 2); }
该表达式首先将 10% 乘以 5,然后将其加到 15% 乘以 2 的结果上。这个设置很好,但乍一看并不容易理解,考虑到复杂的计算,可能确实难以立即明白。将其写成带有括号的形式后,表达式就容易理解了:
E { height: calc((10% * 5) + (15% * 2)); }
你还可以使用嵌套的calc()函数来实现相同的结果。
在表达式中使用乘法或除法时,你必须在操作数周围插入一个空格字符——如果没有这样做,表达式将无效,属性也会被忽略。以下代码展示了两次写出的表达式:第一个是无效的,因为操作数周围没有空格;第二个是格式正确的,因此有效。
E { border-width: calc(1em*10); } /* Invalid */
E { border-width: calc(1em * 10); } /* Valid */
元素尺寸
元素的大小通常通过width或height属性来设置,或者通过它们的max*-*和min*-*变体,结合绝对值(px)、相对值(em)或百分比值。虽然这些选项对于大多数日常使用来说已经足够,但我常常发现有时候我希望盒模型能更灵活一些,或者能更好地感知周围的元素。CSS3 引入了新的属性和值,旨在通过盒模型切换和新的内容感知尺寸方法提供这种额外的灵活性。
盒模型尺寸
多年来,Internet Explorer 实现的盒模型违反了 W3C 规范。W3C 模型规定 width 值表示内容框的宽度,任何填充和边框都是额外的。而在 IE 的模型中,width 值等于元素的总宽度,包括任何填充和边框。请看以下样式规则:
E {
border: 5px;
padding: 10px;
width: 100px;
}
在 IE 模型中,内容框的宽度为 70px,而在 W3C 模型中,它则是完整的 100px。
虽然标准模型更为逻辑,但有时 IE 模型更方便使用。在 CSS3 中,你可以选择使用 IE 模型,方法是使用 box-sizing 属性,详细说明可以参考 CSS3 基本用户界面模块(www.w3.org/TR/css3-ui/)。其语法如下:
E { box-sizing: keyword; }
默认关键字是content-box,这意味着只将指定的width或height应用于内容框,就像在 W3C 模型中一样。相比之下,替代值border-box意味着任何指定的长度都应包括任何填充和边框框。
图 16-1 显示了两者的区别。顶部示例使用了 W3C 盒模型,而底部示例应用了 border-box 值。正如你所看到的,底部示例的总宽度等于顶部示例的内容框宽度。

*图 16-1:比较 *box-sizing* 值:默认的 *content-box* *(顶部) 和 *border-box* (底部)
注意
有些人喜欢设置一个全局样式规则,将 *border-box* 应用于所有元素。我更倾向于根据需要才应用这个规则。
内在与外在尺寸
在网页布局中,一个挑战是元素对其内容以及使用上下文毫无感知——换句话说,若没有 JavaScript,元素无法感知其子元素或父元素的尺寸。CSS3 引入了一个新概念,通过添加内在和外在尺寸,稍微改变了这个局面。内在尺寸基于元素的子元素,而外在尺寸基于父元素的尺寸。这些尺寸模型在 CSS3 内在与外在尺寸模块中有所定义(dev.w3.org/csswg/css-sizing/)。
所有内在和外在尺寸模型都通过在 width 或 height 属性上使用关键词值来应用(以及它们的 min- 和 max- 变体)。例如,以下列表展示了如何将新尺寸模型应用于 width:
E { width: keyword; }
max-content 和 min-content
第一个新关键词值,max-content 和 min-content,是内在值,它们让元素的宽度或高度与其包含的内容中最大(max-content)或最小(min-content)项的大小相同(对于文本来说,就是最长单词的宽度)。考虑以下包含 img 和 p 元素的 div 元素标记:
<div>
<img src="foo.png">
<p>…</p>
</div>
假设 img 元素的宽度为 200px,p 的宽度为 300px。如果 div 元素的 width 值为 max-content,它将刚好足够宽以容纳 p,而如果其值为 min-content,它将刚好足够宽以容纳 img,且 p 中的文本会换行。
比较 图 16-2 中显示的结果。左侧的容器元素应用了 max-content 值,这使它的宽度与最宽的子元素(p)相同,而右侧的容器元素应用了 min-content,使它的宽度与最窄的子元素(img)相同。

图 16-2: *max-content* 值(左)使元素宽度与其最宽的子元素相同,而 *min-content* (右) 使元素宽度与其最窄的子元素相同。
注意
截至目前,当前的桌面和移动版本的 Chrome、Firefox 和 Safari 支持这些关键词,尽管每个浏览器需要厂商前缀(即 *-moz-min-content* 和 *-webkit-min-content*)。目前,Internet Explorer 和旧版智能手机不支持这些关键词。
fit-content
下一个内在关键字值可能是最有用的。它叫做 fit-content,它让元素像浮动元素或表格单元格一样调整大小:元素将扩展,刚好足够容纳其内容,除非元素的最大宽度已达到,此时,内容将换行。
图 16-3 比较了 fit-content 与 max-content 和 min-content 的效果。左上方的框使用了 fit-content,当内容达到父容器的限制时,内容会换行。相反,右上方的框使用了 max-content,因此它会扩展以适应内容——然而,该框现在超出了父容器的宽度,父容器的 overflow 属性值为 hidden,意味着该框被裁剪了。
左下角的框也应用了 fit-content,因此容器会调整大小以适应内容的宽度;右下角的框应用了 min-content,因此容器的宽度仅与 img 元素一样,文本内容会换行。

图 16-3: *fit-content* 值,与 *max-content* 和 *min-content* 进行比较
fill
规范中的最后一个关键字叫做 fill。(但在 Firefox 中是 available,在 Chrome 中是 fill-available!)这个外部值使元素填充其父容器的可用高度或宽度。
假设你想让一个带有边框和内边距的内联块级 p 元素,扩展到和它的父元素一样宽。通常,你会应用以下规则:
p {
border-width: 0 0.5em;
display: inline-block;
padding: 0 1em;
width: 100%;
}
然而,正如你所知,一个元素的“真实”宽度还包括内边距和边框,因此,在这种情况下,p 元素会超出其父容器的范围。一种解决方案是使用 box-sizing 属性(请参见 “Box Sizing” 在 第 203 页),但你可能有充分的理由保持标准的盒模型,所以更好的替代方案是使用内在尺寸:
p { width: fill; }
结果如 图 16-4 所示;带有边框和内边距的内联块元素被调整大小以填充其父容器中的可用空间。

图 16-4:使用 *fill* 值来设置 *width* 使得如图所示的内联块标题填充其父容器的可用宽度。
总结
在本章中,我讨论了与根字体大小和视口相关的值单位、动态计算值以及基于内容和上下文对元素进行尺寸调整的 CSS3 方法。我还讨论了 CSS3 对不同盒模型的切换。
虽然单位和尺寸方法可能看起来是 CSS 中不太引人注目的部分,但它们能为你提供对布局的精细控制,这是非常值得欢迎的。由于网站会在各种不同的设备上查看,能让元素感知其视口、内容和上下文,并以最合适的方式呈现内容,是极其有价值的。
值和尺寸:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 根元素相对单位 | 是 | 是 | 是 | 是 |
| 视口相对单位 | 是 | 是 | 是^(*) | IE9(†),IE10(‡) |
| 计算值 | 是 | 是 | 是 | 是 |
| 盒子模型尺寸 | 是 | 是 | 是 | 是 |
| 内部/外部尺寸 | 是^(§) | 是^(§) | 是^(§) | 否 |
- iOS 中的
vh支持有问题
† 支持vm代替vmin,不支持vmax
‡ 不支持vmax
§ 带有供应商前缀
第十七章:17
GRIDLAYOUT

网格是一个基本的设计技巧。自中世纪以来,书法家就使用简单的网格,而现代排版网格自 20 世纪下半叶开始使用。近年来,已经有一些努力将基于网格的设计引入到网页中,许多框架使用浮动、内边距和外边距来模拟印刷的可能性,尽管这些努力总是显得有些脆弱的临时解决方案。
然而,最近浏览器开始实现原生的 CSS 网格布局系统,具体内容可以参考网格布局模块 (www.w3.org/TR/css-grid-1/)。该模块提供了一系列专门设计用于在屏幕上创建网格的属性,这意味着开发者不再需要将它们从现有的属性和行为中拼凑出来。
网格布局模块中的属性范围非常广泛,因此我将重点讲解最直接有用的部分,而不会陷入那些可能会造成混淆的细节中。在合适的地方,我会标明我省略了一些细节。
网格术语
在介绍新的 CSS 网格语法之前,我将解释一下在网格布局模块中使用的一些术语。即使你认为自己对排版网格已经很熟悉,也请花时间阅读这些定义,因为 CSS 网格中使用的术语与传统排版网格有很大不同。
以下是网格布局模块中使用的关键术语:
网格容器 作为边界的容器元素,定义网格的维度。
网格线 行和列之间的分隔线。这些线是概念性的,而非实际存在的。
网格轨道 行和列的简写名称。网格中创建的每一列或行都被称为 轨道。轨道是线条之间的空间。
网格单元格 每一列和行的交点都创建一个 单元格,它们就像表格中的单元格一样。
网格区域 一个单元格或多个单元格,标记了 网格项 将要放置的区域。
网格项 每一个放置在网格中的子元素。
网格是通过首先在网格容器上设置若干线条来创建的一系列轨道。然后,网格项通过将线条作为坐标来定位在轨道上,从而创建区域,具体如 图 17-1 所示。

图 17-1:一个简单的 3×3 网格,展示了 CSS 网格布局语法中使用的核心术语
网格线是单元格之间的线条。它们定义了一系列行和列,并为坐标定位编号。(我会在本章中逐步解释这个概念。)
声明和定义网格
创建网格的第一步是声明 网格容器,即作为网格基础的元素。网格容器的尺寸是网格的边界,网格的所有属性都应用于它。要声明网格容器,可以使用 display 属性,并将新值设置为 grid,如下所示:
E { display: grid; }
这条声明创建了一个块级的网格容器。下一步是定义其轨道(行和列)。你可以在 显式网格 中定义轨道,设置精确数量的列和行,或者在 隐式网格 中定义轨道,这个网格是根据其内容自动生成的。你也可以将显式网格和隐式网格结合使用,我将依次解释这两者。
通过设置轨道大小来创建显式网格
在显式网格中,你可以通过设置一对属性:grid-template-columns 和 grid-template-rows 来定义特定数量的网格轨道及其大小。每个属性的值是一个由空格分隔的长度列表,用于设置列的宽度或行的高度。例如,以下代码片段创建了一个三列的网格,其中第一列和最后一列设置为网格容器宽度的 20%,第二列设置为 60% 的宽度:
E { grid-template-columns: 20% 60% 20%; }
你可以使用百分比或任何长度单位,包括称为 分数(fr) 的特殊网格长度单位。1fr 等于网格中任何未分配长度的等份。让我来解释一下我的意思。看看这段代码,其中网格容器的宽度为 600px,三个列的宽度各自已定义:
E {
display: grid;
grid-template-columns: 100px 100px 200px;
width: 600px; }
列的总宽度是 400px,比容器的宽度少了 200px。在这种情况下,添加一个宽度为 1fr 的列将使该列的宽度与剩余空间相等,即 200px:
E { grid-template-columns: 100px 100px 200px 1fr; }
再添加一个相同宽度的列,使得两个列的宽度各自为 100px:
E { grid-template-columns: 100px 100px 200px 1fr 1fr; }
将其中一列设置为 3fr 的宽度意味着剩余的宽度将被分为四等份,每份 50px,这样 1fr 等于 50px,3fr 等于 150px:
E { grid-template-columns: 100px 100px 200px 1fr 3fr; }
回到本节的第一个示例,你可以将百分比替换为 fr 单位,以实现相同的效果:
E { grid-template-columns: 1fr 3fr 1fr; }
注意
关于使用分数布局网格的优势,请参见 第 213 页的“分数与百分比”部分。
这段代码实际上定义了三个网格线,另外一个网格线会自动在书写方向的开始位置(对于从左到右书写的语言来说就是左边)创建。这些网格线创建了三个垂直的网格轨道或列,如 图 17-2 所示。

图 17-2:一个简单的三列网格,比例为 1:3:1(为清晰起见添加了网格线和编号)
添加行的方式与此相同。例如,要创建三个行,第一行高度为 60px,第二行的值为 auto,使其根据内容高度自动调整,第三行高度为 5em,你可以使用以下代码:
E { grid-template-rows: 60px auto 5em; }
结合这些属性可以让你完全定义你的网格。例如,这段代码创建了一个基本的 3 列 3 行的网格,共计九个单元格:
E {
display: grid;
grid-template-columns: 1fr 3fr 1fr;
grid-template-rows: 60px auto 5em;
}
这个网格的列按 1:3:1 的比例分布,行的高度是:顶部为 60px,底部为 5em,中间行的高度设置为自动,以适应其内容。最终生成的网格大致如图 17-3 所示。

图 17-3:一个 3×3 的显式网格(为清晰起见添加了线条和数字)
分数与百分比
当单独使用时,百分比和分数是可以互换的。例如,在这段代码中,这两个规则产生相同的结果:
E { width: 50% 50%; }
F { width: 1fr 1fr; }
它们的区别在于,当与 px 或 em 等长度单位混合使用时。假设你有一个包含 15em 列的网格,并且你希望用两个相同大小的列填充剩余的空间。使用百分比,你实际上无法做到这一点,除非你知道容器的宽度,并且愿意进行一些复杂的计算。你可能会想使用 calc()(参见第十六章)来实现这一点:
E { grid-template-columns: 15em calc(50% - 7.5em) calc(50% - 7.5em); }
但规范并未明确 calc() 是否被允许,而且当前的网格实现(截至本文编写时)并不允许这样做。
在这些情况下,分数比百分比更有用。回想一下,网格的分数(fr)会为任何尚未分配的空间留出一个相等的份额,因此你为这个示例编写的基于分数的代码就清晰了:
E { grid-template-columns: 15em 1fr 1fr; }
在这里,网格中任何未分配的宽度将被平分成两个相同大小的列。
尽管如此,尽管你有时可能能在网格中轻松使用百分比,但分数更简单。
在显式网格中放置项目
每个网格容器的直接子元素都会成为一个网格项,并应当放置在网格中。为此,你需要通过一组放置属性来为该项目分配一个单元格坐标。其中第一个属性是 grid-column-start 和 grid-row-start,每个属性的值都是一个整数。这个数字指代网格轨道的起始线(无论是列还是行),多个轨道引用组合起来就形成了单元格的坐标。
例如,要将一个项目放置在第二行第二列的单元格中,你可以使用这段代码(图 17-4 显示了结果):
F {
grid-column-start: 2;
grid-row-start: 2;
}

图 17-4:一个项目被放置在网格中的第二行第二列(为清晰起见添加了线条)
grid-column-start 和 grid-row-start 属性的默认值都是 1,因此省略其中一个值会将项目放置在第一行或第一列。例如,以下代码将项目放置在第一行第二列的单元格中,如图 17-5 所示:
G { grid-column-start: 2; }

图 17-5:第二个项目被放置在网格中的第一行第二列(为清晰起见添加了线条)
默认情况下,项目只会适配到指定的单元格,如果内容超出单元格,则会垂直溢出。你可以通过使用grid-column-end和grid-row-end属性,让项目扩大尺寸,创建一个覆盖多行或多列的区域。就像它们的对等属性一样,这些属性接受一个单一的整数值,指定单元格应结束的线条。例如,要让项目跨越三行,从第 1 行开始,到第 4 行结束,代码如下:
F {
grid-row-start: 1;
grid-row-end: 4;
}
默认情况下,项目会放置在第一列;它从第 1 行开始,到第 4 行结束,意味着它跨越三行,如图 17-6 所示。

图 17-6:一个位于网格中,跨越第一列三行的项目(为清晰起见添加了线条)
作为刚才展示方法的替代方案,你可以使用span关键字,后面跟着项目跨越的轨道数量。重写后的规则如下:
F { grid-row-end: span 3; }
当你想对网格项目的起始行保持不确定性,但始终希望它跨越相同数量的列时,span关键字就变得非常有用。
网格放置简写属性
写四个单独的属性来将元素放置在网格中似乎有些冗长,实际上,简写属性将使你的代码更加简洁。相关的属性是grid-column和grid-row,每个属性的语法相同。第一个,grid-column是grid-column-start和grid-column-end的简写,中间用斜杠分隔;grid-row同理,它是grid-row-start和grid-row-end的简写。
我将演示如何使用它们。看一下应用于同一元素的所有单独属性:
F {
grid-column-start: 2;
grid-column-end: 3;
grid-row-start: 1;
grid-row-end: span 3;
}
使用简写属性,你可以用更简洁的方式编写这些规则:
F {
grid-column: 2 / 3;
grid-row: 1 / span 3;
}
如果即使是两个属性对你来说也太复杂了,实际上你可以将所有这些指令合并为一个简写规则grid-area,它涵盖了所有四个属性。基本语法如下:
F { grid-area: row-start / column-start / row-end / column-end; }
插入适当的值会得到这个非常简洁——尽管可以说,阅读起来可能更难——的规则:
F { grid-area: 1 / 2 / span 3 / 3; }
重复网格线
尽管简单的网格适用于一些实际情况,但更复杂的网格能够提供对内容的更精细控制。在大型排版网格中,拥有超过 12 列是很常见的,每列之间通常有一个空隙(空白区域)。使用网格布局语法定义 12 列网格可能会显得重复,正如你在这段代码中看到的,我已经映射出了 12 列,每列宽度为 1fr,它们之间有 10px 的空隙:
E { grid-template-columns: 1fr 10px 1fr 10px 1fr 10px 1fr
10px 1fr 10px 1fr 10px 1fr 10px 1fr 10px 1fr 10px 1fr 10px 1fr 10px 1fr; }
你可以使用repeat()函数来避免在使用更大网格时重复这种类型的操作。该函数接受两个参数:一个整数,用于设置重复的次数,后跟一个逗号分隔符,以及要重复的网格线值。例如,以下规则创建了与前一个示例相同的网格,但更简洁;它定义了一个 1fr 宽的轨道,然后使用repeat()创建一个模式,其中包含一个 10px 的间隙,后跟一个 1fr 列,重复十一次,总共创建了 12 列,每列宽度为 1fr。
E { grid-template-columns: 1fr repeat(11, 10px 1fr); }
命名的网格区域
除了基于坐标将项目放置到网格中外,你还可以使用grid-template-areas属性将项目放置到命名区域中。使用此属性,你可以通过一系列唯一的标识符在文本字符串中为网格区域命名。这里,我将展示我的意思:
E {
➊ display: grid;
➋ grid-template-areas: 'a b c';
➌ grid-template-columns: repeat(3, 1fr);
}
这两条规则现在应该很熟悉了:第➊行将元素设置为网格容器,第➌行创建三个宽度为 1fr 的列。第➋行使用grid-template-areas属性为每一列命名:在这个由空格分隔的字符串中,每个标识符(a,b,c)依次与列匹配。该输出如图 17-7 所示。

图 17-7:用命名区域制作的三列
要使用命名区域放置项目,你需要将该区域的标识符作为grid-area属性的值。例如,要将项目放置到我的示例网格的中间(b)列,我使用以下代码:
F { grid-area: b; }
你不必像我在这里所做的那样使用单个字符来命名区域;你可以使用任何字符串,只要它们不包含空格。例如,为了让你的内容更加易读,你可能希望描述每个区域的用途。下面是一个例子:
E { grid-template-areas: 'nav main side'; }
F { grid-area: main; }
每一串标识符代表一个网格的行,因此要添加新的一行,只需添加一个新字符串。如果你在同一字符串中多次使用相同的标识符,该区域将跨越相应数量的列。如果你在不同的行的相同位置使用相同的标识符,该区域将跨越相应数量的行。你可以通过以下代码理解我的意思;在第一行中,有一列被称为nav,两列被称为head,所以head区域将跨越两列;第二行也有一个名为nav的第一列,因此nav区域将跨越第一列的两行:
E {
display: grid;
grid-template-areas:
'nav head head'
'nav main side';
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 80px auto;
}
使用此代码,你可以将网格项放置到跨越多个轨道的区域中。在以下代码片段中,元素*F*被放置到head区域,这意味着它跨越第一行的第二列和第三列,而元素*G*将被放置到nav区域,使其跨越第一列的第一行和第二行。这在图 17-8 中有所展示。
F { grid-area: head; }
G { grid-area: nav; }

图 17-8:放置在命名区域中的网格项(为清晰起见添加了线条)
警告
如果你使用多个字符串标识符,必须在每个网格中使用相同数量的列;否则,规则将被声明为无效并被忽略。
grid-template 简写
为了避免写三个单独的规则来定义网格(grid-template-columns、grid-template-rows 和 grid-template-areas),你可以使用 grid-template 简写。这个简写让你在没有命名区域的情况下定义列和行变得简单。以下是一个示例:
E { grid-template: grid-template-columns / grid-template-rows; }
要与命名网格区域一起使用该属性,你需要在斜杠后添加标识符,如这个示例所示:
E { grid-template: repeat(3, 1fr) / 'nav head head'; }
如果你还想为行定义高度,可以在每个标识符字符串后面添加行的长度值。让我们回顾一下上一节中定义的完整网格:
E {
grid-template-areas:
'nav head head'
'nav main side';
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 80px auto;
}
如果使用 grid-template 简写书写,这个网格的样子如下:
E {
grid-template: repeat(3, 1fr) / 'nav head head' 80px 'nav main side';
}
注意
我没有在第二个命名区域字符串后声明行高,因为它默认为 *auto*。
隐式网格
隐式网格是由其内容定义的,而不是由显式网格的指定长度值定义。当你不关心网格中有多少行或列,只关心网格中的每个项目都有位置时,你可以使用 grid-auto-columns 和 grid-auto-rows 属性。每个属性接受一个单一的值来指定行或列的宽度。例如,这段代码表示任何创建的列应该宽 1fr,任何新行应该高 80px:
E {
display: grid;
grid-auto-columns: 1fr;
grid-auto-rows: 80px;
}
现在任何具有 grid-column 或 grid-row 值的项目都将被放置到网格中,网格会自动调整其大小以适应这些项目,同时保持所有列和行的固定大小。例如,以下代码显示了一个网格项目,它被设置为从第一行的第二列开始,并跨越两行两列。网格将扩展以适应这个项目,正如你在图 17-9 中看到的那样。
F {
grid-column: 2 / 4;
grid-row: 1 / span 2;
}

图 17-9:由其中包含的项目创建的隐式网格(添加了线条以便更清晰)
没有声明位置的网格项目
如果网格容器中的子元素没有声明 grid-column 或 grid-row 值,它们将会发生什么?它们会回退到默认值 1,并堆叠在第一行、第一列的同一单元格中。
你可以通过 grid-auto-flow 属性改变这种默认行为,它确保没有指定位置的项目会被插入到网格中有空余位置的地方。你还可以控制它们的放置位置。以下是这个规则的基本形式:
E { grid-auto-flow: keyword; }
关键字可以是column或row。如果使用column,项目将填充列中的空白单元格,向下移动;如果使用row,项目将填充行中的空白单元格,横向移动。例如,在图 17-10 中,左侧的容器的grid-auto-flow值为column,因此未被放置的项目将填充每列中的空白单元格,直到当前列填满,然后跳到下一列。而右侧的容器的值为row,因此项目将横向填充行,直到该行填满,然后移动到第二行。

图 17-10:比较自动流动:(左)项目流入列,(右)项目流入行
显式网格与隐式网格的结合
当你创建显式网格时,可能会发现可用的网格轨道数量少于你所需的项目数。假设你有一个三列的网格,但某个网格项应该跨越四列:
E { grid-template-columns: repeat(3, 1fr); }
F { grid-column: 1 / 5; }
在这种情况下,网格将扩展以容纳项目创建的轨道;一个额外的列将被添加到网格中,总共有四列。你可以通过grid-auto-columns和grid-auto-rows属性来设置这些额外轨道的大小。
以下代码创建了一个三列两行的显式网格,并通过添加隐式网格来允许任何超出该显式网格的项目。隐式网格中的额外列被定义为宽度为 1fr,额外行的高度为 80px:
E {
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(2, 80px);
grid-auto-columns: 1fr;
grid-auto-rows: 80px;
}
现在,任何放置在该网格中的项目将填充一个与显式网格的维度匹配的区域。
网格简写
定义一个同时具有显式和隐式属性的网格可能会导致规则列表非常庞大。例如,以下代码显示了一个元素,规则创建了一个带有命名区域的显式网格,并且添加了隐式网格属性,以便容纳可能扩展网格的任何项目,总共有六条规则:
E {
grid-template-areas: 'a b b' 'a c d';
grid-template-columns: repeat(3, 1fr);
grid-template-rows: 80px auto;
grid-auto-flow: row;
grid-auto-columns: 1fr;
grid-auto-rows: 80px;
}
幸运的是,针对这一系列规则,有一个简写属性可以使用。这个简写叫做grid——然而,你只能使用它来设置显式网格或隐式网格,而不能同时设置两者。要用它来设置隐式网格,可以使用以下语法:
E { grid: grid-auto-flow grid-auto-columns / grid-auto-rows; }
所以,以下是之前代码中显示的隐式网格规则的简写:
E { grid: row 1fr / 80px; }
设置显式网格的grid语法与本章之前看到的grid-template属性完全相同。既然如此,以下是该节开始时所示的显式网格规则的简写:
E { grid: repeat(3, 1fr) / 'a b b' 80px 'a c d'; }
你可能会觉得奇怪,两个简写属性竟然做的是完全一样的事情。我只能同意你的看法。
网格项堆叠顺序
在将项目放置到网格中时,区域有时会发生重叠。为了处理这种情况,你可以创建堆叠顺序来定义项目在网格中的堆叠方式。例如,你可以指定,第三行开始的项目应该堆叠在第一行开始的项目之上,无论它们在 DOM 中的顺序如何。
你可以使用z-index属性来改变堆叠顺序。z-index值最高的项目将堆叠在所有其他项目之上。例如,下面的标记显示了两个将成为网格项的div元素:
<div class="grid-item item-one">…</div>
<div class="grid-item item-two">…</div>
我将两个项目都放入网格中,但通过添加以下代码,我确保*item-one*会堆叠在*item-two*之上,因为它的起始列和行比*item-two*的更大:
.item-one {
grid-column: 2 / 4;
grid-row: 2;
}
.item-two {
grid-column: 1 / 3;
grid-row: 1 / 3;
}
你可以在图 17-11 左侧的示例中看到结果:*item-one*堆叠在*item-two*上方。但如果你像这样增加*item-two*的z-index值:
.item-two { z-index: 2; }
你会看到*item-two*现在堆叠在*item-one*上方,如图 17-11 右侧所示。

图 17-11:比较堆叠顺序:(左)堆叠跟随位置,(右)通过*z-index*设置堆叠顺序
另一种方法是使用order属性,它作为 Flexbox 模块的一部分被引入(参见第十五章)。在显式网格中,这个属性的作用与z-index完全相同,改变堆叠顺序;然而,在隐式网格中,它也改变了项目在网格中的放置顺序。
你可以在图 17-12 中看到这个效果,在这里我将三个网格项(*item-one*、*item-two*和*item-three*)放入一个grid-auto-flow值为column的网格中。在左边的网格中,项目按它们在 DOM 中的出现顺序流入网格,但在右边的网格中,两个项目的顺序发生了变化,如图 17-12 所示。
.item-one { order: 2; }
.item-two { order: 3; }

图 17-12:在隐式网格中使用*order*属性进行堆叠:(左)按照 DOM 顺序,(右)通过*order*属性设置顺序
Internet Explorer 的 Grid Layout 语法
Internet Explorer 10 是第一个实现 Grid Layout 属性的浏览器,尽管它使用的语法已经过时。所以,如果你小心使用,仍然可以在 IE10 和 IE11 中复制某些网格布局,但有非常严格的限制——最显著的是你只能创建显式网格。
所有 IE 网格属性都使用-ms-前缀,display属性的值也是如此:
E { display: -ms-grid; }
你可以使用-ms-grid-columns和-ms-grid-rows属性来创建轨道,这些属性类似于grid-template-columns和grid-template-rows。区别在于重复轨道线的方式:在为 IE 设计时,你需要将宽度值放在括号中,并在后面加上方括号中的重复次数:
E {
-ms-grid-columns: (1fr)[3];
-ms-grid-rows: (80px)[2];
}
网格项目通过 -ms-grid-column 和 -ms-grid-row 属性进行定位,这些属性类似于 grid-column 和 grid-row,但仅允许使用单一的数值。要跨多个单元格放置项目,必须使用 -ms-grid-column-span 和 -ms-grid-row-span 来设置项目应跨越的轨道数量(类似于 span 关键字)。
在这种情况下,代码块中应用于元素 *E* 和 *F* 的规则在功能上是相同的:
E {
-ms-grid-column: 1;
-ms-grid-column-span: 2;
-ms-grid-row: 2;
-ms-grid-row-span: 3;
}
F {
grid-column: 1 / span 2;
grid-row: 2 / span 3;
}
截至本文写作时,IE 的语法没有命名区域,也没有与 grid-column-end 或 grid-row-end 等效的属性。
总结
网格布局是迈向全新网页内容布局方式的第一步。网格布局、弹性盒子布局(Flexbox)和媒体查询的结合,使得可以实现丰富的布局,并能够适应不同的设备和视口,开创了一个摆脱浮动技巧和限制性标记的新网页设计世界。
网格布局:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 网格布局 | 不支持^(*) | 不支持 | 不支持 | IE10^(†) |
- 已实现,但默认关闭
† 语法与规范不同;带有厂商前缀
第十八章:18
混合模式、滤镜效果与遮罩

目前大多数浏览器——桌面版和移动版——都支持可缩放矢量图形(SVG)格式。与 GIF、JPEG 和 PNG 等图像格式(即位图图像)不同,SVG 是由一种标记语言构成的
(类似 HTML)它描述的是点或矢量,而不是构成位图图像的像素网格。SVG 相较于位图图像的一个优势是,作为一种矢量格式,它能很好地适配各种分辨率的屏幕。除此之外,SVG 格式提供了一套丰富的图形效果,这些效果以前仅在 Photoshop、GIMP 和 Sketch 等图像编辑软件中才有。
然而,直到最近,在浏览器中使用这些图形效果并不实际;实时图像效果计算密集,使用它们会对 Web 性能产生负面影响。然而,如今浏览器的速度和能力大大提升,几乎所有图形渲染都直接在设备的 GPU 上进行。这一进展使得硬件加速的变换和动画显示成为可能,也释放了使用 SVG 丰富图形效果的潜力。
随着浏览器实现了 SVG 图形效果,允许其他 Web 技术也能够访问这些效果变得非常自然。因此,在本章中,你将了解三个使 SVG 图形潜力可用于 CSS 的特性:混合模式、滤镜效果和遮罩。这些非破坏性效果只会改变图像在页面上的显示方式;它们不会修改源图像。
注意
许多 SVG 效果涉及颜色的变化,这些变化在黑白显示中很难呈现。我强烈建议你亲自查看这些效果的示例,访问 thebookofcss3.com/。
混合模式
如果你使用过专业级的图像编辑软件,比如 Photoshop 或 GIMP,你可能已经熟悉混合模式。混合模式是一种将图像与纯色或其他图像混合的方式,让两者看起来融合或混合在一起。
提供了多种混合模式,每种模式根据不同的算法以不同的方式混合图像。CSS 中可用的模式定义在《合成与混合模块》(www.w3.org/TR/compositing-1/)。由于篇幅限制,我无法详细介绍所有模式,因此将集中讲解三种:
屏幕 在此模式下,白色保持白色,而黑色则让背景色透过。因此,应用此模式后,图像通常会变得更亮。
叠加 该模式在“屏幕”与“叠加”模式之间找到平衡。高光和阴影得以保留,增强了对比度。
覆盖 该模式在“屏幕”和“叠加”模式之间取得了平衡。高光和阴影被保留下来,从而增强了对比度。
剩余的混合模式通常是这三种的变体。你可以在规格文档中阅读每种模式的详细说明,或者查看一个很好的视觉介绍,链接地址为 dev.opera.com/articles/getting-to-know-css-blend-modes/。
background-blend-mode
background-blend-mode 属性用于混合元素的背景层;例如,你可以用它将背景颜色与背景图片混合。此属性仅在元素的上下文中工作:只有背景层会被混合;元素本身不会与其下方的页面部分混合。该属性的值需要是你想要使用的混合模式的关键词,如 screen、multiply 或 overlay。例如,下面是如何应用 Multiply 混合模式:
E { background-blend-mode: multiply; }
background-blend-mode 的默认值是 normal,这意味着背景层不会进行混合。
混合图片与颜色
展示背景混合模式如何工作的最简单方法是将背景图片与背景颜色混合。下面的代码展示了一个元素,应用了背景图片和颜色,并设置为使用 Screen 混合模式:
E {
background: url('foo.png') #f00;
background-blend-mode: screen;
}
图 18-1 显示了此效果和其他混合模式的结果。左上角的图像(A)没有应用任何混合模式,作为参考提供。其他图像分别应用了不同的混合模式关键词,即 screen(B)、multiply(C)和 overlay(D)。(注意,在这种情况下,Overlay 模式只是将纯色覆盖在图像上,效果远不如预期;Overlay 混合模式在混合两张图片时要更有用。)

图 18-1:比较应用不同混合模式的源图像
混合两张图片
除了将背景图片层与颜色层混合外,你还可以将背景图片层与其他背景图片层混合。要做到这一点,首先应用多个背景图片(如 第八章 所讨论的),然后像之前一样设置混合模式:
E {
background-color: transparent;
background-image: url('foo.png'), url('bar.png');
background-blend-mode: multiply;
}
图 18-2 显示了混合两张图片的示例。每张图片应用了不同的混合模式关键词。从左到右,分别是 screen、multiply 和 overlay。(如果你在桌面浏览器中查看示例文件,鼠标悬停在元素上,可以看到我使用的两张不同背景图片。)

图 18-2:多背景图片层的混合
请注意,我已在元素上设置了透明的背景颜色。如果没有这样做,multiply 关键词会让下层背景图片与背景颜色混合,然后再与其他图片层混合,从而产生与我原本想要的效果不同的结果。
多种混合模式
由于你可以为一个元素添加多个背景图像,因此你可以为每一层应用混合模式。为此,你只需在一个逗号分隔的值列表中列出所需的混合模式。
在下面的代码中,该元素有三个背景层:两张图像和一种颜色。我已经应用了两个混合模式关键词:Multiply 模式将用于将背景颜色与 foo.png 混合;结果将与 bar.png 使用 Screen 混合模式混合。
E {
background-color: #f00;
background-image: url('foo.png'), url('bar.png');
background-blend-mode: multiply, screen;
}
注意
与其他多个背景属性一样,如果 *background-blend-mode* 属性的值比背景层数少,则该值列表会循环。
改变混合模式可以产生截然不同的效果。例如,图 18-3 显示了多种组合应用于不同元素,背景层相同。左侧的元素在顶部图像层使用 Screen 模式,在底部使用 Multiply 模式;中间的元素在顶部图像层使用 Multiply 模式,在底部使用 Overlay 模式;右侧的元素在顶部使用 Overlay 模式,在底部使用 Screen 模式。正如你所看到的,结果差异显著。

图 18-3:以不同的组合应用多个混合模式
mix-blend-mode
尽管混合背景图层无疑是有用的,但真正的强大之处在于将一个元素与另一个元素进行混合。在 CSS3 中,你可以通过 mix-blend-mode 属性来实现这一点。应用于元素时,该属性会将元素的内容与屏幕上直接位于其后方的任何元素的内容和背景混合。
为了理解这与 background-blend-mode 的区别,让我们看一个简单的例子。在下面的代码中,我为元素 *E* 应用一个背景图像,并为嵌套在 *E* 内的元素 *F* 应用 Multiply 混合模式:
E { background-image: url('foo.png'); }
F { mix-blend-mode: multiply; }
图 18-4 显示了结果,并进行其他混合模式的比较。左上角的元素(A)没有应用混合模式,用作参考;其余三个分别应用了混合模式关键词:screen(B)、multiply(C)和 overlay(D)。

图 18-4:应用于文本元素的不同混合模式,与其父元素的背景混合
隔离
使用 mix-blend-mode 时,请注意,它会与屏幕上绘制在其后面的 每个 可视元素进行混合,这可能会导致意外的后果。例如,假设你有以下标记结构,其中 img 元素嵌套在 div 内,而 div 又嵌套在 body 内:
<body>
<div>
<img src="foo.jpg">
</div>
</body>
现在,你在 body 上设置一个背景图像,并在 img 上设置 mix-blend-mode 属性:
body { background-image: url('bunny.png'); }
img { mix-blend-mode: screen; }
在这种情况下,img 将与 body 混合,因为 div 是透明的,如 图 18-5 左侧的示例所示。

图 18-5:隔离一个元素以设置新的堆叠上下文
如果结果不是你想要的,你可以使一个元素创建一个新的堆叠上下文,^(1),类似于将 position: relative 设置在元素上时会重置绝对定位的坐标。混合时,这一过程称为 隔离 元素,你需要使用 isolation 属性:
E { isolation: isolation-mode; }
默认值是 auto,但要创建新的堆叠上下文,你必须使用另一个值 isolate:
div { isolation: isolate; }
你可以在 图 18-5 右侧的示例中看到差异。div 被隔离,并创建了一个新的堆叠上下文,因此 img 只与透明的父元素进行混合。因此,你不会像左侧示例中那样看到 body 的背景图像。
滤镜效果
像混合模式一样,滤镜是专业图像编辑软件中的常见部分。滤镜用于在元素到达页面之前改变其外观,并且与混合模式不同,它们不依赖于两个组件的交互。CSS 滤镜在 CSS 滤镜效果模块中定义 (www.w3.org/TR/filter-effects-1/),并通过 filter 属性应用,如下所示:
E { filter: function; }
*function* 值至少是九种滤镜效果函数中的一种。每个函数接受一个参数,除非需要一系列参数(以空格分隔的列表)。我在这里讨论每一个函数。
blur()
对元素应用模糊效果。blur() 函数的参数是一个长度单位,用于控制模糊的半径。该效果被称为 高斯模糊——一种图像平滑效果,可以减少图像中的噪点。半径值越大,模糊效果越明显。例如,要创建半径为 10px 的模糊效果,你可以使用以下代码:
E { filter: blur(10px); }
你可以在 图 18-6 中看到模糊效果的实际应用。左侧是未经过滤的图像;右侧是应用了 blur() 滤镜的图像。

图 18-6:应用高斯模糊滤镜
brightness() 和 contrast()
brightness() 函数改变元素的亮度,而 contrast() 函数增加或减少元素的明暗对比度。这两个函数都接受百分比作为参数。以下是一个示例:
E { filter: brightness(50%); }
E { filter: contrast(50%); }
在这两种情况下,100% 的参数使元素保持不变。brightness() 的 0% 参数使元素完全黑色,而 contrast() 的 0% 参数使元素完全灰色。你可以使用超过 100% 的值来增加亮度和对比度。
注意
你也可以为这个函数使用一个数字,其中 *1* 相当于 *100%*,*1.5* 相当于 *150%*,以此类推。所有其他接受百分比值的函数也适用此规则。
图 18-7 展示了两个函数的示例。第一张图片没有应用任何滤镜,作为参考展示。中间的图片应用了 brightness() 函数,右侧的图片应用了 contrast() 函数—每个函数的参数都是 50%。

图 18-7:使用 CSS 滤镜效果调整亮度和对比度
grayscale(), sepia(), 和 saturate()
接下来的三个函数都与颜色处理有关。grayscale() 函数提供了一种将颜色替换为灰度的方式,帮助你将图像转换为黑白图像。sepia() 着色函数与 grayscale() 类似,只不过它使用金色调来产生复古照片效果。最后,saturate() 函数控制颜色的强度。
每个函数都接受一个百分比值作为参数:
E { filter: grayscale(100%); }
E { filter: sepia(100%); }
E { filter: saturate(200%); }
grayscale() 函数的 100% 值使图像完全黑白。将 100% 传递给 sepia() 函数会使图像完全呈现褐色调。在这两个函数中,0% 会保持图像不变,而大于 100% 的值将被当作 100% 处理。
saturate() 函数也接受一个百分比值作为参数,但它的工作原理与 grayscale() 和 sepia() 不同。0% 会让图像完全去饱和—也就是灰度图像,而大于 100% 的值会使图像过度饱和。
注意
要查看这些函数的实际效果,请访问本书配套网站上的示例文件 18-a (thebookofcss3.com/)。
hue-rotate()
hue-rotate() 函数名虽然平凡,但它用于旋转元素的色调。回想一下在 “色相、饱和度、亮度” 中提到的内容,在 第 116 页,色相是通过围绕色轮轴的角度来计算的。因此,hue-rotate() 函数所需的参数是一个度数,像这样:
E { filter: hue-rotate(45deg); }
应用 hue-rotate() 函数实际上是将元素中所有颜色的色调按相同的角度围绕色轮进行旋转。要查看该函数的实际效果,请访问本书网站上的示例文件 18-b。
opacity()
opacity() 函数的工作方式与 第十章 中介绍的 opacity 属性相同。该函数接受一个百分比值作为参数,其中 0% 代表完全透明,100% 代表完全不透明:
E { filter: opacity(25%); }
大于 100% 的值将被当作 100% 处理。
如果 opacity() 函数的结果与 opacity 属性完全相同,那为什么还要使用它呢?因为你可以将它与其他滤镜效果函数结合使用,正如你将在 “多个滤镜效果函数” 中看到的,详见 第 234 页。
drop-shadow()
起初,drop-shadow() 函数可能看起来与第九章 中介绍的 box-shadow 属性相同。实际上,它接受与 box-shadow 相同的值作为参数:x 偏移量、y 偏移量、模糊半径和阴影颜色。以下是一个示例:
E { filter: drop-shadow(5px 5px 3px gray); }
这两者之间最大的区别是 drop-shadow() 函数会识别目标元素的任何 alpha 值(透明度)。你可以在 图 18-8 中清晰地看到这种区别。目标图像具有透明背景,因此在应用了 drop-shadow() 函数的左侧图像中,阴影框跟随图像的轮廓。而右侧的图像则应用了 box-shadow 属性。由于 box-shadow 属性不考虑 alpha 透明度,阴影只跟随盒子的轮廓。

图 18-8:比较 *drop-shadow()* 滤镜(左)与 *box-shadow* 属性(右)
多个滤镜效果函数
你可以通过将多个滤镜效果函数按空格分隔列出,来应用多个滤镜效果函数。例如,你可以同时向元素添加模糊和投影效果:
E { filter: blur(5px) drop-shadow(5px 5px 3px gray); }
函数列出的顺序很重要,因为它们将按照此顺序应用。例如,在接下来的示例中,应用了两个滤镜效果,但我更改了顺序:在第一个示例中,gray-scale() 函数先于 sepia() 应用,而在第二个示例中,我将其顺序颠倒了:
E { filter: sepia(100%) gray-scale(100%); }
E { filter: gray-scale(100%) sepia(100%); }
在第一个示例中,sepia() 函数将先应用,然后是 grayscale(),因此 sepia() 滤镜效果的所有颜色都会转换为灰度。第二个示例中,grayscale() 函数先应用,再是 sepia(),所以 sepia() 滤镜效果的颜色会显示。此书网站上的示例文件 18-c 显示了这种效果。
就像 CSS 变换(在 第十二章 中介绍)一样,当你在 filter 属性中列出多个函数时,列表中未列出的任何函数将返回默认值。例如,在以下示例中,当元素悬停时,它失去了 sepia() 滤镜效果:
E { filter: sepia(100%) blur(2px); }
E:hover { filter: blur(5px); }
SVG 中的滤镜
回想一下,CSS 滤镜效果实际上是 SVG 滤镜预设的简写。滤镜效果模块显示了所有 CSS 函数的标记等价物。例如,blur() 滤镜的 SVG 标记如下所示(*blur-radius* 值是无单位的数字):
<filter>
<feGaussianBlur stdDeviation="blur-radius" />
</filter>
你可以在 SVG 中创建自己的滤镜,并通过使用 ID 引用在 CSS 中应用它们。第一步是为你的滤镜添加一个 ID 值:
<filter id="blur">…</filter>
然后,在你的 CSS 中使用 url() 表示法,包含 ID 引用,作为 filter 属性的值。如果你的 SVG 与文档中的标记 inline,则只需要 ID 引用:
E { filter: url('#blur'); }
如果你的 SVG 在外部资产文件中(例如,filters.svg),则需要指定该文件的路径,并跟上 ID 引用:
E { filter: url('filters.svg#blur'); }
与 CSS 滤镜效果不同,这种技术只适用于单个滤镜。要将多个滤镜应用到一个元素,必须先在 SVG 标记中将它们组合起来。
遮罩
遮罩是一种技术,其中元素的某些部分被隐藏。遮罩有两种方法:剪切,隐藏区域由一个覆盖在元素上的多边形形状设置,以及图像遮罩,使用图像的 alpha 通道来设置隐藏区域。
剪切
剪切是最简单的遮罩形式。在剪切时,一个形状覆盖在图像上,任何位于形状后面的元素部分会被显示,而任何超出形状边界的部分则会被隐藏。形状的边界被称为剪切路径,可以通过clip-path属性来创建:
E { clip-path: shape; }
*shape*可以是四种基本类型中的一种,每种类型通过一个函数来表示:circle()、ellipse()、inset()(用于矩形)和polygon()。每个函数接受多个参数,用于定义剪切路径。例如,要创建一个圆形,您需要为circle()函数提供三个参数,使用类似径向渐变的语法(参见第十一章):
E { clip-path: circle(r at cx cy); }
*r*表示圆形的半径,而*cx*和*cy*是圆心的坐标。因此,要将元素裁剪为位于目标中心的 100px 圆形,您需要使用以下值:
E { clip-path: circle(100px at 50% 50%); }
ellipse()函数非常相似,只需要额外一个参数来设置椭圆的半径:
E { clip-path: ellipse(rx ry at cx cy); }
这里*rx*用来表示半径的* x 轴,*ry*表示 y *轴。要创建一个半径为 50px 和 100px 的椭圆,并且将其放置在目标的中心位置,您需要使用这些值:
E { clip-path: ellipse(50px 100px at 50% 50%); }
要查看这些在实际中的效果,请参见图 18-9。左侧的示例使用circle()函数,采用前面使用的值,右侧则使用ellipse(),同样采用前面使用的值。

图 18-9:*clip-path*属性的函数值: *circle()* (左)和 *ellipse()* (右)
如前所述,inset()函数用于创建一个从应用它的元素边框内缩的矩形。它可以接受最多 12 个参数!前四个参数设置矩形每一边的偏移距离——就像border-image-slice属性一样(参见第九章)。因此,单个值会在所有边上设置相等的偏移距离;如果提供两个值,第一个设置上下偏移,第二个设置左右偏移;以此类推。
如果我们使用所有四个值,语法将如下所示:
E { clip-path: inset(o1 o2 o3 o4); }
每个*o**表示一个偏移值(分别对应上、右、下、左)。
在最简单的格式下,单个值会创建四个相等的偏移量,如此例所示,每个偏移量值为 2em:
E { clip-path: inset(2em); }
你还可以圆化剪切路径的角落,这时剩下的参数就派上用场了。在 round 关键字之后,你可以使用与 border-radius 属性相同的语法(请参见第九章)来定义每个角的半径——最多可以设置八个值来进行精细控制。为了避免在这里重复解释 border-radius 的简写语法,本例展示了如何为之前定义的内嵌矩形的每个角设置 20px 的半径:
E { clip-path: inset(2em round 20px); }
查看图 18-10 中的结果。左侧示例显示的是一个有直角的矩形,右侧则是一个有圆角的矩形。

图 18-10:没有(左)和有(右)边框圆角值的 *inset()* 函数
你可以使用 polygon() 函数来创建更复杂的剪切路径。该函数接受任意数量的参数,成对出现,以逗号分隔的列表形式传递。每一对参数代表一个坐标值,所有坐标值组合起来用于绘制所需的剪切形状。举个简单的例子,这个语法定义了一个三角形的三个点:
E { clip-path: polygon(0% 100%, 100% 0%, 0% 0%); }
三角形的三个顶点位于目标元素的左上角、左下角和右上角。你可以在图 18-11 中看到剪切效果。

图 18-11:使用 *polygon()* 函数创建的三角形剪切路径
注意
剪切元素仅改变其视觉外观——被剪切元素的尺寸和盒模型不会被修改。有关此功能未来可能变化的建议,请参见 “Shapes” 第 246 页(page 246)。
Safari 中 clip-path 的实现
clip-path 属性在 Safari 7 中首次实现,并带有厂商前缀,但使用的是现在已过时的语法版本(更新的语法在 Safari 8 中得以实现)。polygon() 函数的工作方式保持不变,但其他函数稍有不同;例如,circle() 函数的语法略有不同,要求三个用逗号分隔的参数:
E { -webkit-clip-path: circle(cx, cy, r); }
类似地,ellipse() 函数接受四个参数:
E { -webkit-clip-path: circle(cx, cy, rx, ry); }
inset() 函数被称为 inset-rectangle(),它至少需要四个用逗号分隔的值来表示偏移距离:
E { -webkit-clip-path: inset-rectangle(o1, o2, o3, o4); }
你可以圆化角落,但每个角的值必须相同——不能为每个角设置不同的值。然而,你可以设置 x 轴和 y 轴的值来创建不规则的圆角半径:
E { -webkit-clip-path: inset-rectangle(o1, o2, o3, o4, rx, ry); }
与标准的最大区别在于 rectangle() 函数;这个函数最初在规范中定义,但后来被推迟到未来版本中。rectangle() 函数也可以创建矩形剪切形状,但使用坐标而不是偏移值。它最多接受六个参数:
E { -webkit-clip-path: rectangle(x, y, w, h, rx, ry); }
前四个参数是必需的:*x* 和 *y* 设置形状的左上角的 x 和 y 坐标,相对于目标元素的左上角;*w* 和 *h* 分别设置形状的宽度和高度。可选的 *rx* 和 *ry* 参数用于设置剪切路径所有角落的圆角半径。(你可以使用一个值来设定相等的半径,但不能单独设置每个角落。)
图 18-12 显示了这六个值如何设置图像的剪切区域。

图 18-12:设置剪切区域的 *rectangle()* 形状函数的六个值
如果我们按照以下代码组合这些选项,我们将创建一个剪切路径,该路径距离元素的左上角 10px,宽度为 50%,高度为 100px,角落的半径在两个轴上均为 20px。图 18-13 显示了这将是什么样子。
E { -webkit-clip-path: rectangle(10px, 10px, 50%, 100px, 20px); }

图 18-13:使用 *rectangle()* 函数和 *-webkit-clip-path* 制作的矩形
剪切路径动画
由于剪切路径是通过坐标定义的,因此动画化它们以创造令人印象深刻的效果变得非常简单。例如,你可以拿前一节中定义的三角形多边形,在鼠标悬停时进行过渡:
E {
clip-path: polygon(0% 0%, 0% 100%, 100% 0%);
transition: clip-path 1s;
}
E:hover { clip-path: polygon(100% 100%, 0% 100%, 100% 0%); }
在这个动画中,三角形的两个点保持在相同的位置,而第三个点切换到对角线的另一角落。这通过过渡动画实现,产生了你在示例文件 18-d 中看到的效果。
SVG 中的剪切路径
你还可以在 SVG 中创建剪切路径,并通过 CSS 将其应用于元素。为此,你首先定义剪切路径的标记,然后赋予它一个唯一的 ID。例如,以下代码创建了一个圆形剪切路径,该路径使用 clipPath 元素定义,位于元素的中心,半径为宽度和高度的一半。它的 ID 为 clipping。
<defs>
<clipPath id="clipping">
<circle cx="0.5" cy="0.5" r="0.5" />
</clipPath>
</defs>
一旦定义好,我会通过 clip-path 属性将这个剪切路径应用于我的元素,属性值等于包含剪切路径 ID 的 url() 语法:
E { clip-path: url('#clipping'); }
不幸的是,这种方法有一些缺点。首先,截止到目前,它仅在 Firefox 中有效。此外,你不能在不使用 JavaScript 的情况下对这些形状进行动画效果处理。
图像遮罩
除了使用几何形状剪切元素外,你还可以使用另一张图像作为遮罩,利用遮罩的 alpha 值来确定目标中有多少部分是可见的。(这个过程类似于混合模式的工作方式,只是它使用 alpha 值而不是黑白色。)遮罩是通过 mask 属性应用的,它接受以下值:
E { mask: image position / size; }
*image*值是url()表示法,指向用作遮罩的图像路径。属性*position*和*size*的工作方式与background-position和background-size属性相同(见第八章)。例如,以下代码会将一个名为mask.png的图像放置在元素的中心,遮罩填充父元素的宽度,同时保持原始的纵横比:
E { mask: url('mask.png') 50% 50% / 100% auto; }
图 18-14 展示了遮罩的三个步骤。目标图像在左边;用作遮罩的图像在中间;应用到目标上的遮罩在右边。

图 18-14:图像遮罩的三个步骤
注意
这是一个简单的遮罩。语法比这更加灵活,尽管在本书中无法详细探讨,因为它有些复杂。
mask属性是许多子属性的简写,包括mask-image、mask-position和mask-size。除了这些属性外,你还可以使用更多属性,完整的mask简写如下:
E { mask: image mode position / size repeat origin clip composite; }
让我们简单回顾一下这些不太熟悉的属性。mask-mode属性决定遮罩是基于默认的 alpha 通道工作,还是通过亮度(光亮度)工作;mask-repeat像background-repeat一样平铺遮罩图像;mask-origin和mask-clip也像它们的背景等价物(background-origin和background-clip在第八章中讨论过);而mask-composite控制多个mask-image值在重叠时的交互方式。
边框遮罩
在第九章中,我介绍了border-image属性,用于将图像应用到元素的边框上。你可以使用我们在该章节中讨论的相同方法,通过切片图像来将遮罩应用到元素的边框。
与边框遮罩相关的属性有mask-border-source、mask-border-slice、mask-border-repeat、mask-border-width和mask-border-output。每个属性都可以包含在mask-border简写中,并且它们的功能与border-image-*对应属性相同。例如,要设置一个名为mask.png的图像,切片为每个 40px,并沿元素的每一边重复,使用以下语法:
E { mask-border: url('mask.png') 40px repeat; }
不幸的是,截止目前,没有浏览器完全按照规范支持这一功能(虽然 Chrome 和 Safari 支持一个类似但过时的属性,叫做-webkit-mask-box-image),因此我不会再花时间讨论边框遮罩。
SVG 中的遮罩
你可以使用 SVG 来遮罩图像,就像你可能裁剪它们一样。为此,你需要在标记中定义遮罩,然后使用 CSS 属性(mask)应用它。例如,以下代码创建了一个黑色方框的遮罩,中间有一个白色椭圆:
<defs>
<mask id="masking">
<rect y="0.3" width="1" height=".7" fill="black" />
<circle cx=".5" cy=".5" r=".35" fill="white" />
</mask>
</defs>
下一步是将遮罩应用到目标元素,使用mask属性,并通过url()表示法包含mask元素(masking)的 ID:
E { mask: url('#masking'); }
注意
想深入了解 SVG 和 CSS 中的蒙版,请参见 Dirk Schulze 的《CSS 蒙版》(www.html5rocks.com/en/tutorials/masking/adobe/)。
结合滤镜效果和蒙版
如果你想将滤镜效果与同一元素的蒙版结合使用,重要的是要知道它们会按特定顺序应用:首先应用滤镜效果,然后是剪裁,接着是蒙版,最后是不透明度。为了看到这个顺序的后果,可以考虑以下代码,它对元素应用了一个阴影滤镜和圆形剪裁路径:
E {
clip-path: circle(50% at 50% 50%);
filter: drop-shadow(5px 5px black);
}
在这段代码中,效果按照以下顺序应用到图像上:首先应用阴影效果;然后将应用了阴影的图像剪裁成圆形。为了避免剪裁掉阴影,你可以将滤镜效果应用到父元素上:
D { filter: drop-shadow(5px 5px black); } /* parent */
E { clip-path: circle(50% at 50% 50%); } /* child */
之所以能这样工作,是因为浏览器解析 DOM 渲染的方式。子元素的剪裁会在从父元素继承的滤镜效果之前发生。(图 18-15 比较了这两种方法。)

图 18-15:比较图形 CSS 效果的渲染顺序
总结
混合模式、滤镜效果和蒙版为 Web 浏览器提供了以前只有在专业照片处理软件中才能找到的功能。每一种效果单独使用都很有用,但将它们结合起来,设计师和开发者就能以全新的方式进行 Web 上的视觉设计。
使用这些图形效果的能力得益于 SVG 的广泛应用。描述 SVG 图像的标记与 HTML 紧密相连(可以嵌入 HTML 中,并作为文档的一部分解析),未来你会看到 SVG 和 CSS 的结合更加紧密——我们已经在本章中的图形效果中看到了这一点,当前正在开发的模块也描述了在第十四章中提到的动画属性的共享实现。
混合模式、滤镜效果和蒙版:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
background-blend-mode |
支持 | 支持 | Safari 8 | 不支持 |
mix-blend-mode |
不支持^(*) | 支持 | Safari 8 | 不支持 |
isolation |
不支持* | 不支持 | Safari 8 | 不支持 |
filter |
支持^(†) | 不支持^(‡) | 支持^(†) | 不支持 |
clip-path |
支持* | 不支持 | Safari 8^(§) | 不支持 |
mask |
支持^(†) | 不支持 | 支持^(†) | 不支持 |
- 已实现,但默认关闭
† 带有厂商前缀
‡ 可以使用 SVG 中定义的滤镜
§ 带有厂商前缀;在 Safari 7 中使用过时的语法实现
第十九章:19
CSS 的未来

在本章的最后,我们将介绍一些更具实验性的 CSS3 特性。正如你将看到的,这些特性涵盖了从大大扩展你页面布局能力(可以与印刷杂志相媲美)到为你带来其他编程语言中强大方法和功能的特性。
我必须首先说明,这些特性目前的实现有限,并且在我写这篇文章时,这些特性仍然在模块中定义,尚未完成 W3C 推荐周期;这两个事实意味着本章中的特性未来尚不确定。由于这些特性处于不断变化的状态,我将在此简要讨论每个特性,而不如本书其他部分那样详细。
形状
CSS 的一个限制是所有元素都是矩形的。即使你使用圆角或裁剪元素(如第十八章所讨论),底层的框依然是矩形的。因此,举例来说,围绕圆形或裁剪元素浮动的内容会遵循该元素矩形的轮廓框,正如你在图 19-1 中看到的那样。

图 19-1:尽管元素这里有圆角,但围绕它浮动的内容依然遵循其矩形边界框。
CSS 形状模块(*www.w3.org/TR/css-shapes/)解决了这个问题,它允许你将几何形状应用到元素上,使得内联内容可以围绕这些形状的轮廓流动,而不是原本的边界框。shape-outside 属性定义了要应用于元素的形状:
E { shape-outside: shape-function; }
*shape-function* 值可以是 clip-shape 属性(在“裁剪”一节中介绍,见第 235 页)上允许的任何函数:inset()、circle()、ellipse() 或 polygon()。例如,这段代码创建了一个半径为 100px、中心位于应用该属性元素的左上角的圆形:
E { shape-outside: circle(100px at left top); }
当该元素被浮动时,围绕它的任何内联内容都会围绕圆形的轮廓流动,正如图 19-2 所示。
请注意,形状仅适用于元素的浮动轮廓;背景色、图像和边框仍然会遵循元素的边框框。正如你在图 19-3 中看到的,我已经去掉了浮动元素的 border-radius 值,导致文本与元素的边框重叠。为避免此问题,你可以考虑使用与定义形状匹配的裁剪路径(参见第 235 页的“裁剪”一节)。

图 19-2:应用了 CSS 形状的元素。内容围绕定义的圆形轮廓流动,而不是围绕边界框。

图 19-3:如果没有匹配的 *border-radius* 值,浮动文本会与形状元素的背景颜色重叠。
为了在浮动形状的边缘与环绕其内容之间创建额外的空间,您可以使用shape-margin属性。此属性接受任何长度值。例如,这段代码为形状周围添加了额外的 0.5em 空间:
E { shape-margin: 0.5em; }
请注意,与margin属性不同,您只能添加一个单一的边距值,这个值会均匀地应用于形状的所有边。
排除
截至目前,要让内容围绕一个元素流动,您必须使该元素浮动。缺点是元素只能浮动到左侧或右侧;您不能将其定位在文本的中间,也没有太多精细控制。
CSS 排除模块 (www.w3.org/TR/css3-exclusions/)解决了这个问题。CSS 排除定义了一种文本环绕任意元素的方式,无论该元素本身是否浮动。图 19-4 展示了一个元素定位在一些文本上方。在左侧的示例中,元素与文本没有交互,而在右侧,它充当了一个排除元素——它的表现就像是浮动了,文本在其两侧流动。

图 19-4:左侧是元素定位在文本上方;右侧是一个浮动的排除元素,文本围绕它流动。
您可以使用wrap-flow属性创建一个排除元素,该属性接受一个关键字值:
E { wrap-flow: flow-direction; }
*flow-direction*关键字设置了元素周围任何内联内容流动的侧面。您可以通过多个值来控制流动;这些值在图 19-5 中有显示:
• clear值意味着内容不会围绕排除元素的任何一侧流动(A)。
• both值使内联内容围绕元素的两侧流动(B)。
• start值意味着内联内容会围绕元素的左侧流动(如果文档或元素的书写方向是从左到右),并留出右侧空白(C)。
• end值则与start相反:内联内容会围绕元素的右侧流动,留出左侧空白(D)。
minimum和maximum关键字会使内容围绕元素的一侧流动,距离该侧与其包含元素的最近边缘分别为较小或较大的距离。使用图 19-5 中的示例,maximum等同于start(C),minimum等同于end(D)。
默认的关键字值是auto,意味着不会创建排除——内容会流动到元素下方,如图 19-4 左侧所示。
在我写这段文字时,Internet Explorer 10 和 11 支持 CSS 排除,使用-ms-wrap-flow属性,并且没有minimum关键字值。

图 19-5:内容根据使用不同的关键字值围绕排除元素流动。
区域
第七章解释了 CSS 列,这是跨多个连续列流动内容的方法。CSS 区域模块 (dev.w3.org/csswg/css-regions/) 扩展了这一理念,允许内容流入多个区域,这些区域在布局顺序中不必是连续的,且不受页面位置的限制。可以想象一个长篇杂志文章,内容跨越多个页面,尽管这些页面不一定是连续的。
CSS 区域通过定义一个内容区域并为其分配一个唯一标识符(称为命名流),以及使用flow-into属性,使这一切成为可能:
E { flow-into: foo; }
一旦该标识符被分配,元素*E*的内容就可以流入多个区域。为此,使用命名流作为flow-from属性的值;例如,如果你想将*foo*的内容流入元素*F*和*G*,你可以使用如下代码:
F, G { flow-from: foo; }
*E*的内容首先流入*F*,因为它在代码中排在前面。如果内容溢出*F*,溢出的部分将流入*G*。图 19-6 展示了内容流入三个独立区域。(我添加了背景阴影以便更容易识别每个区域。)

图 19-6:文本流入第一个框,溢出到第二个框,然后再溢出到第三个框。
元素*F*和*G*可以放置在页面上的任何位置;它们不需要是连续的(尽管你应该确保内容的视觉流程易于用户跟随)。
截至本文写作时,CSS 区域已在 Safari 6.1 及以上版本中实现,并使用-webkit-前缀。CSS 区域也在 Internet Explorer 10 和 11 中实现,使用-ms-前缀,并附带要求:内容流动的元素必须是iframe。
变量
几乎每种编程语言都可以定义变量——符号名称,可以为其分配值,且可以在后续引用或更新。CSS 预处理器(如 Sass)的广泛流行部分原因在于它们允许在 CSS 中使用变量。这种流行直接促成了自定义属性的创建——这是一种在原生 CSS 语言中类似变量的有限功能。这些功能在 CSS 自定义属性级联变量模块中介绍 (*www.w3.org/TR/css-variables-1/)。
你通过三步定义一个自定义属性:设置其作用域(可以应用的值的范围),创建一个唯一标识符,然后为该标识符分配一个值。以下是代码的表现方式:
➊ :root {
➋ --fooColor: #f00;
}
暂且不论➊,看一下➋,在这里我使用唯一标识符*--fooColor*定义了自定义属性。(CSS 变量名必须是没有空格的字符字符串,并以双短横线作为前缀,以避免与其他已定义的值冲突。)我已经将颜色值#f00赋给了自定义属性,但任何有效的 CSS 属性值都是允许的。
一旦定义并赋值了自定义属性,你可以将其作为任何其他属性的值使用。赋给自定义属性的值将作为引用该属性的属性的值,通过在var()函数中使用唯一标识符作为参数。例如,要将该值应用于border-color和color,你可以这样写:
E {
border-color: var(--fooColor);
color: var(--fooColor);
}
在这里,自定义属性*--fooColor*的值(即#f00)将作为*E*的border-color和color属性的值。当然,当应用自定义属性时,属性值必须有效:比如用颜色值设置width属性就没有意义。如果你在规则中使用了无效的自定义属性,该规则将被忽略。
现在,让我们回到之前代码中的➊。这一行设置了变量的作用范围。在我的例子中,作用范围是:root选择器,这意味着变量具有全局作用域,可以应用于任何元素,无论其在 DOM 中的位置。如果你希望自定义属性仅在应用于某些元素时有效,你可以限制这个范围。例如,要使自定义属性仅在应用于h1元素时有效,你可以像这样设置它的范围:
h1 { --fooColor: #f00; }
通过这种方式限制范围,任何来自h1选择器的对自定义属性的引用都会以#f00的color值显示:
h1 { color: var(--fooColor); }
但是,如果你从超出范围的元素(如h2)中引用自定义属性,由于该自定义属性是未知的,所以下面的规则将被忽略:
h2 { color: var(--fooColor); }
截至本文写作时,自定义属性已在 Firefox 31 及以上版本中可用,并且在 Chrome 中通过标志实现,但由于存在某些性能问题,可能没有启用。因此,自定义属性的未来充满不确定性。
功能查询
媒体查询,在第二章中介绍,已经改变了我们使网站在不同屏幕尺寸和分辨率下响应式工作的方法。它们的强大之处部分在于其逻辑的简洁性:如果查询条件满足,则查询声明块中定义的规则将被应用。这个思想在 CSS 条件规则模块中得到了进一步发展(* www.w3.org/TR/css3-conditional/*),该模块扩展了查询,不仅使用基于设备/用户代理属性(如尺寸和分辨率)的条件,还允许定义仅在支持特定 CSS 特性的情况下才应用的 CSS 规则。
这些新的条件规则,称为 特性查询,通过新的 @supports 规则定义。@supports 语法类似于 @media,不同之处在于它需要一个 CSS 属性-值对(或多个)作为参数。如果浏览器支持该属性-值对,定义的规则将会应用。
例如,要测试浏览器是否支持 Flexbox,测试条件是浏览器是否识别值为 flex 的 display 属性:
@supports (display: flex) {…}
在已实现 Flexbox 的现代浏览器中(当然也实现了 @supports),属性-值对会被识别,花括号内的规则会被应用。没有 Flexbox 支持的旧版浏览器将无法识别该属性-值组合,并会跳过这些规则。
与媒体查询一样,你可以在特性查询中使用逻辑运算符——and、or 和 not。例如,要查看浏览器是否同时支持 Flexbox 和 Transitions,可以使用 and:
@supports (display: flex) and (transition: 1s) {…}
如果一个属性有多个使用厂商前缀的实现,而你想测试浏览器是否支持其中的任意一个实现,可以使用 or:
@supports (animation-duration: 1s) or (-webkit-animation-duration: 1s) {…}
如果你想查看浏览器是否不支持某个特定属性,可以使用 not:
@supports not (display: flex) {…}
截至本文撰写时,@supports 规则已在 Chrome 和 Firefox 中实现,并列为“开发中”状态,Internet Explorer 也已列出该规则。Webkit 已经实验性地实现了该规则,但截至目前,它在 Safari 8 中尚未发布。在这些浏览器中,@supports 声明块中的规则将被忽略。
设备适配
实现响应式设计时,使用媒体查询的一个关键部分是能够设置视口属性,方法是使用视口 meta 标签(请参见“设备宽度和高度”,第 15 页)。不幸的是,视口 meta 标签并非没有问题,其中最严重的问题是最初的实现没有文档化,这意味着其他浏览器厂商不得不对其进行逆向工程,从而导致了小的互操作性问题。CSS 设备适配模块(*www.w3.org/TR/css-device-adapt/)试图通过将视口 meta 标签属性转换为原生 CSS,并对功能进行文档化和标准化,同时添加新的功能来解决这些互操作性问题。
设备适配模块通过新的 at-rule @viewport 重新实现了视口 meta 标签:
@viewport {…}
@viewport 规则接受多个属性-值对或 视口描述符,它们设置视口的参数。其中一些描述符是现有的 CSS 属性,而其他的则是 @viewport 特有的。
在许多情况下,这些描述符与视口 meta 标签中使用的参数不同,但它们执行相同的角色。例如,要在 meta 标签中将宽度设置为设备宽度,可以写成这样:
<meta name="viewport" content="width=device-width">
为了使用@viewport规则执行相同的操作,您可以使用width属性,值为100vw——即视口的完整宽度(vw单位在第十六章中有讨论):
@viewport {
width: 100vw; }
@viewport规则的最佳部分是,您可以将其与媒体查询结合使用,为不同的设备配置创建自定义视口。例如,您可以仅在小屏幕上将宽度设置为设备宽度:
@media (max-width: 480px) {
@viewport {
width: 100vw;
}
}
截至本文写作时,@viewport规则在 Internet Explorer 10 和 11 中作为@-ms-viewport规则实现。同时它也在 Chrome 中实现,但当前默认未启用。
粘性定位
一种相当常见的网页设计模式是让元素变得粘性,这样当页面滚动时,它们会固定在视口的顶部或底部。粘性通常是通过在position属性上设置fixed值,并结合 JavaScript 中的滚动事件来实现的,但 CSS3 的定位布局模块(*dev.w3.org/csswg/css-position-3/)引入了一个原生的sticky值来实现同样的功能。
粘性定位是一种相对定位和固定定位的混合体。元素的定位是相对的,直到它达到粘性阈值,这个阈值是视口的预设滚动位置。例如,在下面的代码中,元素*E*在视口从顶部向下滚动 20px 时变为粘性:
E {
position: sticky;
top: 20px;
}
截至本文写作时,粘性定位在 Safari 6.1 及以上版本中已实现,使用-webkit-sticky值,在 Firefox 中没有前缀。预计 Chrome 将实现粘性定位,且在 Internet Explorer 中被列为“正在考虑中”。
更多更多功能
CSS 是一个不断发展的标准,无论是由 W3C、浏览器厂商、像 Adobe 这样的相关方,还是由 Web 开发社区推动,都在持续开发。除了本章讨论的新特性之外,还有许多令人兴奋的 CSS 发展正在被提议、讨论和开发。其中一些包括但不限于以下内容:
• 设置不同形状的角落,并允许剪裁或部分线条作为边框
• 能够将来自 Flexbox 的定位关键字(第十五章)应用于任何元素,从而引起元素定位方式的重大变化
• 允许颜色轻松着色或着色的颜色函数,并提供更简单的灰度操作
• 扩展自定义属性的概念,包括自定义选择器或函数
• 基于文本行的网格设计,旨在更好地放置内容中的元素,具有垂直节奏
• 包括精灵和后备图像的图像值,及可以使用元素作为背景的图像
这些功能大多仍处于理论阶段,但它们应该能让你了解 CSS 未来发展的思维水平。而且,网页行业变化的速度如此之快,几个月后你阅读这些内容时,肯定会有更多的新特性被提出。
结论
CSS 显然在不断发展。从最初作为文本文件简单装饰的方式开始,CSS 正朝着一个未来发展,几乎成为一种语言,能够适应我们将来用于访问网页的各种设备。
在我多年的 CSS 工作、写作和讨论经验中,我发现变革往往来自意想不到的方向。某些看似有用的模块或属性由于各种原因将长期没有实现——无论是因为商业决策、资源问题,还是纯粹的政治原因。而变革的驱动力来自不同的源头:从 W3C 和浏览器厂商到像 Adobe 这样的公司,它们希望按自己的方式塑造网络,再到开发社区,它们开发工具以满足网页开发的需求。你永远不知道下一次重大变革将从哪里来。
我在本书中没有涵盖 CSS3 模块的所有内容,但希望我已经涵盖了足够的内容,至少能够激起你对一些你今天就可以开始使用的新技术的好奇心,以及 CSS 未来的发展方向。我鼓励你保持对网页样式元素讨论的关注,下载浏览器的预览版本,进行自己的实验,并将实验结果反馈给浏览器开发者和 W3C。CSS3(大多数情况下)是围绕网页开发者的需求塑造的,你的意见和反馈至关重要。
未来的 CSS:浏览器支持
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 形状 | 是 | 否 | Safari 8^(*) | 否 |
| 排除项 | 否 | 否 | 否 | IE10^(†) |
| 区域 | 否 | 否 | 是* | IE10^(‡) |
| 变量 | 否 | 是 | 否 | 否 |
| 功能查询 | 是 | 是 | 否 | 否^(§) |
| 设备适配 | 否^(||) | 否 | 否 | IE10^(†) |
| 粘性定位 | 否 | 是 | 是* | 否 |
- 带供应商前缀
† 带供应商前缀;不支持最小值
‡ 带供应商前缀;只能使用 iframe 作为内容源
§ 当前列为“开发中”
|| 已实现,但默认禁用
第二十章:A
当前主流浏览器中的 CSS3 支持

本附录收集了在每章末尾显示的浏览器支持表,提供了本书中涉及的 CSS3 属性和规则的快速概览。
这个列表有些复杂,原因有两个:首先,正如我多次提到的,CSS3 仍处于变化中,某些属性仍然可能会发生变化;其次,浏览器的新版本定期并持续发布,每次发布都带来了大量的新实现。
在以下表格中,我列出了四个主流浏览器(Chrome、Firefox、Safari 和 Internet Explorer)的实现状态。我没有包括 Opera,因为它现在基于 Chrome,并且功能列表大致相同。除非另有说明,否则每个浏览器的移动版与桌面版的功能集相同。
Chrome 和 Firefox 是常青浏览器:它们会自动更新,版本号仅用于内部参考。Internet Explorer 和 Safari 仍然定期发布版本;在我写这篇文章时,IE11 和 Safari 8 是最新版本。
在下表中,是表示该功能在常青浏览器或至少在 Safari(6–8)或 IE(9–11)的最近三个版本中得到支持。否则,将显示版本号。关于 IE,可以安全地假设,除了极少数例外,本书中的几乎所有内容都没有在 IE8 或更低版本中实现。
我会在与本书配套的网站上更新这些表格的最新版本(* www.thebookofcss3.com/ *),所以请定期查看以获取最新的实现状态。
媒体查询(第二章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 媒体查询 | 是 是 | 是 是 |
选择器(第三章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 新的属性选择器 | 是 | 是 | 是 | 是 |
| 一般兄弟组合器 | 是 | 是 | 是 | 是 |
DOM 和属性选择器(第四章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 结构伪类 | 是 | 是 | 是 | 是 |
:target |
是 | 是 | 是 | 是 |
:empty |
是 | 是 | 是 | 是 |
:root |
是 | 是 | 是 | 是 |
:not() |
是 | 是 | 是 | 是 |
伪元素(:: 语法) |
是 | 是 | 是 | 是 |
| UI 元素状态 | 是 | 是 | 是 | 是 |
| 约束验证 | 是 | 是 | 是 | IE10^(*) |
::selection |
是 | 是 | 是 | 是 |
- 不支持
:in-range,:out-of-range
Web 字体(第五章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
@font-face |
是 | 是 | 是 | 是 |
font-size-adjust |
否 | 是 | 否 | 否 |
font-stretch |
否 | 是 | 否 | 是 |
font-feature-settings |
是 | 是 | 否 | IE10 |
font-variant-^(*) |
是* | 否 | 是* | 否 |
- 需要厂商前缀
文本效果和排版样式(第六章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
text-shadow |
是 | 是 | 是 | IE10 |
text-overflow |
是 | 是 | 是 | 是 |
text-align(新值) |
是 | 是 | 是 | 否 |
text-align-last |
是 | 是^(*) | 否 | 是^(†) |
overflow-wrap |
是 | 是^(‡) | 是 | 是^(‡) |
hyphens |
否 | 是* | 是* | IE10* |
resize |
是^(§) | 是 | 是^(§) | 否 |
- 带有厂商前缀
† 没有 start 和 end 值
‡ 类似 word-wrap
§ 在移动浏览器中不可用
多列(第七章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
column-count |
是^(*) | 是* | 是* | IE10 |
column-width |
是* | 是* | 是* | IE10 |
columns |
是* | 是* | 是* | IE10 |
column-fill |
否 | 是* | 否 | IE10 |
column-gap |
是* | 是* | 是* | IE10 |
column-rule |
是* | 是* | 是* | IE10 |
column-span |
是* | 否 | 是* | IE10 |
- 带有厂商前缀
背景图像(第八章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
background-position(边缘值) |
是 | 是 | 是 | 是 |
background-attachment |
是 | 是 | 是 | IE10 |
background-repeat(新值) |
是 | 否 | 否^(*) | 是 |
background-repeat(两个值) |
是 | 是 | 是 | 是 |
| 多重背景图像 | 是 | 是 | 是 | 是 |
background-size |
是 | 是 | 是 | 是 |
更新的 background 属性 |
是 | 是 | 是 | 是 |
background-clip |
是 | 是 | 是 | 是 |
background-origin |
是 | 是 | 是 | 是 |
- 这些值被识别,但未正确显示。
边框和盒子效果(第九章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
border-radius |
是 | 是 | 是 | 是 |
border-image |
是 | 是 | 是 | IE11 |
box-shadow |
是 | 是 | 是 | 是 |
颜色和不透明度(第十章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
opacity |
是 | 是 | 是 | 是 |
| RGBA 值 | Yes | Yes | Yes | Yes |
| HSL 值 | Yes | Yes | Yes | Yes |
| HSLA 值 | Yes | Yes | Yes | Yes |
currentColor值 |
Yes | Yes | Yes | Yes |
渐变(第十一章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 线性渐变 | Yes | Yes | Yes | IE10 |
| 重复线性渐变 | Yes | Yes | Yes | IE10 |
| 径向渐变 | Yes | Yes | Yes | IE10 |
| 重复径向渐变 | Yes | Yes | Yes | IE10 |
2D 变换(第十二章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 2D 变换 | Yes | Yes | Yes^(*) | IE9*, IE10 |
- 带有厂商前缀
3D 变换(第十三章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 3D 变换 | Yes | Yes | Yes^(*) | IE10^(†) |
- 带有厂商前缀
† 不支持transform-style的preserve-3d值
过渡与动画(第十四章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 过渡 | Yes | Yes | Yes | IE10 |
| 动画 | Yes^(*) | Yes | Yes* | IE10 |
- 带有厂商前缀
Flexbox(第十五章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 弹性盒布局 | Yes | Yes | Yes^(*) | IE10^(†), |
- 带有厂商前缀
† 支持较旧的语法,带有厂商前缀
值与尺寸(第十六章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 根相对单位 | Yes | Yes | Yes | Yes |
| 视口相对单位 | Yes | Yes | Yes^(*) | IE9^†, IE10^‡ |
| 计算值 | Yes | Yes | Yes | Yes |
| 盒模型 | Yes | Yes | Yes | Yes |
| 内在/外在尺寸 | Yes^§ | Yes^§ | Yes^§ | No |
- iOS 中对
vh的支持有缺陷
† 支持vm而不是vmin,不支持vmax
‡ 不支持vmax
§ 带有厂商前缀
网格布局(第十七章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 网格布局 | No^(*) | No | No | IE10^(†) |
- 已实现但默认关闭
† 与规范不同的语法;带有厂商前缀
混合模式、滤镜效果和遮罩(第十八章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
background-blend-mode |
Yes | Yes | Safari 8 | No |
mix-blend-mode |
No^(*) | Yes | Safari 8 | No |
isolation |
否* | 否 | Safari 8 | 否 |
filter |
是^† | 否^‡ | 是^† | 否 |
clip-path |
是* | 否 | Safari 8^§ | 否 |
mask |
是^† | 否 | 是^† | 否 |
- 已实现,但默认关闭
† 带有厂商前缀
‡ 可以使用在 SVG 中定义的滤镜
§ 带有厂商前缀;在 Safari 7 中使用过时的语法实现
未来 CSS(第十九章)
| Chrome | Firefox | Safari | IE | |
|---|---|---|---|---|
| 形状 | 是 | 否 | Safari 8^(*) | 否 |
| 排除 | 否 | 否 | 否 | IE10^(†) |
| 区域 | 否 否 | 是* | IE10^(‡) | |
| 变量 | 否 | 是 | 否 | 否 |
| 特性查询 | 是 | 是 | 否 | 否^(§) |
| 设备适配 | 否^(||) | 否 | 否 | IE10^(†) |
| 粘性定位 | 否 | 是 | 是* | 否 |
- 带有厂商前缀
† 带有厂商前缀;不支持minimum值
‡ 带有厂商前缀;仅能使用iframe作为内容源
§ 当前列为“开发中”
|| 已实现,但默认禁用
第二十一章:B
在线资源

在本附录中,我列出了一些有用的文章、资源和工具,帮助大家了解本书中列出的每个新 CSS3 特性。网络上有成千上万的网站提供演示和技巧;随便搜一下 Google,你就会找到 500 页类似的内容。虽然其中有些有用,但许多都相对较差,因此我尽量避免推荐“30 个你可以用 CSS3 做的酷炫事情!”之类的博客帖子,转而提供我认为更具实际价值的资源。
这个列表仍在不断更新中,我计划在附带本书的网站上保持一个更新的列表: www.thebookofcss3.com/。如果你知道任何我应该添加的资源,可以通过网站与我联系并告诉我。
通用 CSS 资源
在我深入具体的文章之前,我想推荐几个在写这本书时对我帮助巨大的网站。如果没有这些网站,我的工作将会困难得多,因此我必须对这些作者表示衷心的感谢。
可能是互联网上最有用的网站,毫无疑问也是我最常参考的网站,就是 Mozilla 开发者网络(MDN)。这个网站由志愿者编写,是学习 HTML、CSS 和 JavaScript 的最佳资源,每个属性都有清晰的描述: developer.mozilla.org/
Alexis Deveria 的网站 Can I Use… 是一个关于不同浏览器中对新兴网页平台功能支持情况的详细数据表。如果你想知道某个功能的支持范围,这里是唯一的地方: caniuse.com/
第二章:媒体查询
• 制作优质媒体查询的基本注意事项: zomigi.com/blog/essential-considerations-for-crafting-quality-media-queries/
• 高效媒体查询的 7 个习惯: bradfrostweb.com/blog/post/7-habits-of-highly-effective-media-queries/
• 使用 CSS 媒体查询进行响应式设计: developers.google.com/web/fundamentals/layouts/rwd-fundamentals/use-media-queries/
第三章和第四章:选择器、伪类和伪元素
• IE Test Drive – CSS3 选择器: ie.microsoft.com/TEStdrive/HTML5/CSS3Selectors/
第五章和第六章:网页字体、文本效果与排版样式
• 更现代的网页排版尺度: typecast.com/blog/a-more-modern-scale-for-web-typography/
• 网页排版风格的元素应用: webtypography.net/
• Google Web 字体: www.google.com/fonts/
• Font Squirrel: www.fontsquirrel.com/
第七章: 多列布局
• 使用 CSS 多列布局创建简单网格: dbushell.com/2014/02/03/simple-grids-with-css-multi-column-layout/
• IE 测试驱动 – 亲手操作:多列布局: ie.microsoft.com/testdrive/graphics/hands-on-css3/hands-on_multi-column.htm
第 8 和 9 章: 背景图像,以及边框和盒子效果
• CSS 精灵:它们是什么,为什么它们很酷,以及如何使用它们: css-tricks.com/css-sprites/
• 谦逊的 border-radius: lea.verou.me/humble-border-radius/
• border-image 生成器: border-image.com/
第十章: 颜色和透明度
• 使用哪种 CSS 颜色系统: demosthenes.info/blog/781/Which-CSS-Color-System-To-Use-Where
• HSL 颜色选择器: www.workwithcolor.com/hsl-color-picker-01.htm
第十一章: 渐变
• CSS3 图案库: lea.verou.me/css3patterns/
• IE 测试驱动 – CSS 渐变背景生成器: ie.microsoft.com/TestDrive/Graphics/CSSGradientBackgroundMaker/
第 12 和 13 章: 2D 和 3D 变换
• CSS3 变换: www.sitepoint.com/series/css3-transformations/
• 变换玩具场: www.westciv.com/tools/transforms/
• 理解 CSS 变换矩阵: dev.opera.com/articles/understanding-the-css-transforms-matrix/
第十四章: 过渡和动画
• 你需要知道的 CSS 过渡: blog.alexmaccaw.com/css-transitions
• Ceaser – CSS 缓动动画工具: matthewlein.com/ceaser/
• Animate.css – 一键加入的 CSS 动画: daneden.github.io/animate.css/
第十五章: 弹性盒布局
• Flexbox 解决方案: philipwalton.github.io/solved-by-flexbox/
• 使用 Flexbox 提升技能: zomigi.com/blog/leveling-up-with-flexbox/
• Fibonacci – Flexbox 编排器: maxsteenbergen.com/fibonacci/
第十六章:数值与尺寸
• CSS 的 rem 单位不仅仅用于字体大小:css-tricks.com/theres-more-to-the-css-rem-unit-than-font-sizing/
• 在现代网站设计中使用 vw 和 vh 度量单位:demosthenes.info/blog/660/Using-vw-and-vh-Measurements-In-Modern-Site-Design/
• 使用 CSS 最小内容设计内外:demosthenes.info/blog/662/Design-From-the-Inside-Out-With-CSS-MinContent/
第十七章:网格布局
• 通过示例学习网格:gridbyexample.com/
• 如何使用 CSS 网格创建自适应布局:msdn.microsoft.com/en-us/library/ie/jj553856%28v=vs.85%29.aspx
• 使用 CSS3 网格布局赋予内容优先级:24ways.org/2012/css3-grid-layout/
第十八章:混合模式、滤镜效果和遮罩
• 浏览器中的 PhotoShop:理解 CSS 混合模式:demosthenes.info/blog/707/PhotoShop-In-The-Browser-Understanding-CSS-Blend-Modes/
• 理解 CSS 滤镜效果:www.html5rocks.com/en/tutorials/filters/understanding-css/
• CSS 遮罩:www.html5rocks.com/en/tutorials/masking/adobe/
第十九章:CSS 的未来
• 使用 CSS 形状创建非矩形布局:sarasoueidan.com/blog/css-shapes/
• 理解 CSS 形状的参考框:razvancaliman.com/writing/css-shapes-reference-boxes/
• CSS 排除模块——让内容流动:www.vanseodesign.com/css/exclusions/
• 即将推出:CSS 功能查询:blogs.adobe.com/webplatform/2014/08/21/coming-soon-css-feature-queries/


浙公网安备 33010602011771号