Skip to content

继承、层叠和优先级

在软件开发中,CSS 是很特别的存在。严格来讲,它不是编程语言,却要求抽象思维。它不是纯粹的设计工具,却要求创造力。
它提供了看似简单的声明式语法,但是在大型项目中写过 CSS 的人都知道它可能会变得极其复杂。

在学习传统编程中遇到问题时,你通常知道该搜索什么(比如,“如果找到一个数组里类型为 x 的元素”)。
在 CSS 中,却很难将问题提炼成一句话。即使可以,答案一般也是“这得看情况”。最好的解决办法通常取决于具体场景,以及你希望以多大粒度处理各种边缘问题。

继承

有一种给元素添加样式的方式: 继承
经常有人把层叠跟继承混淆。虽然两者相关,但是应该分别理解它们。

如果一个元素的某个属性没有层叠值,则可能会继承某个祖先元素的值。
比如通常会给 <body> 元素加上 font-family,里面的所有祖先元素都会继承这个字体,就不必给页面的每个元素明确指定字体了。
下图展示了继承是如何顺着 DOM 树向下传递的。

继承属性从DOM树的父节点传递到后代

但不是所有的属性都能被继承。默认情况下,只有特定的一些属性能被爱继承,通常是我们希望被继承的那些。
它们主要是跟文本相关的属性: color、font、font-family、font-size、font-weight、font-variant、font-style、line-height、letter-spacing、text-align、text-indent、text-transform、white-space 以及 word-spacing

还有一些其他的属性也可以被继承,比如列表属性: list-style、list-style-type、list-style-position 以及 list-style-image。
表格的边框属性 border-collapse 和 border-spacing 也能被继承。
注意,这些属性控制的是表格的边框行为,而不是常用于指定非表格元素边框的属性。(恐怕没人希望将一个 <div> 的边框传递到每一个后代元素)。
以上为不完全枚举,但是已经很详尽了。

我们可以在适当的场景使用继承。比如给 body 元素应用字体,让后代元素继承该字体

1
2
3
body {
  font-family: sans-serif;
}

将属性加到 body 上会让整个网页上生效。而将属性加到特定元素上,则只会被它的后代元素继承。
继承属性会顺序传递给后代元素,直到它被层叠值覆盖

继承似乎有点难以理解,不过却可以节省很多时间。
假设 CSS 属性不会传给内部嵌套的标签,如果一段文字中包含其他标签,例如用于强调的 <strong><em>标签,用于添加链接的<a>标签。
我们定义了一个样式,让这段文字显示成白色,字高为 32 像素,使用 VarelaRound 字体。如果<em><strong><a>标签里的文字还使用默认的单调样式,一定很奇怪。
为了统一,我们还得再定义一个样式,让<em>标签的外观与<p>标签匹配。这样太麻烦了!

继承不仅适用于标签样式,任何类型的样式都能继承。所以,如果在某个标签上应用类样式,标签里的标签会从类样式中继承属性。

继承如何简化样式表

使用继承可以简化样式表。假如想让网页中的所有文字都使用相同的字体,不用分别为每个标签定义样式,只需为<body>标签定义一个标签样式即可(也可以定义一个类样式,然后将其应用到<body>标签上)。
在这个标签样式中指定想用的字体,网页中所有其他的标签都会继承这个字体属性:

1
2
3
body {
  font-family: Arial, Helvetica, sans-serif;
}

使用继承还能把样式属性应用到网页中的某个区域上。例如,很多 Web 设计师会使用<div>标签标识网页中的不同区域,例如横幅、侧边栏和页脚;
如果使用 HTML5 的话,可能会使用某个分区标签,例如<header><aside><footer><article>
在为外层标签定义的样式中,某些 CSS 属性会应用到区域内的所有标签上。如果想让侧边栏中的文字使用相同的颜色,可以在样式中设置 color 属性,然后把样式应用到<div><header><article>等分区标签上。
此时,内部嵌套的<p><h1>等标签都会继承相同的文字颜色。

继承的局限性

美中不足,继承也有缺点: 很多 CSS 属性根本不会传给后代标签。例如,border 属性(在元素四周画边框)就不会继承。当然,这是有道理的。
如果继承,设置了 border 属性的元素里每个标签都会有边框。比如说为 <body> 标签设置了边框,那么每个无序列表和列表里的每个项目都会有边框。

下面举些例子,说明什么时候并不一定会继承:

  • 一般来说,影响元素在页面中所在位置的属性,以及设置元素外边距、背景色和边框的属性不会被继承
  • Web 浏览器渲染标签时会继承各自的内部样式: 标题使用大号粗体字,链接是蓝色,等等。即便为网页中的文字定义了字号,并将其应用到<body>标签上,标题仍然比段落里的文字大,<h1>标签也仍然比<h2>标签的字号大。同理,即便为<body>标签定义了文字颜色,网页中的链接仍然使用 Web 浏览器默认定义的蓝色。

注意: 通常最好不用浏览器内置的样式,这样网站的外观在不同的浏览器中才能尽量保持一致

  • 样式冲突时,更具体的样式胜出。也就是说,如果专门为某个元素设置了 CSS 属性,例如为无序列表指定了字号,而专门设置的属性与继承的属性(例如为<body>标签设置的font-size属性)冲突了,那么浏览器会使用为<ul>标签设置的字号

注意: 样式经常出现这种冲突,浏览器处理这种冲突的规则称为“层叠”

使用开发者工具

当属性值被继承和覆盖时,这个路径会很难追踪。如果你还不熟悉浏览器的开发者工具,请开始养成使用它们的习惯。

使用开发者工具能够看到哪些元素应用了哪些样式规则,以及为什么应用这些规则。
层叠和继承都是抽象的概念,使用开发者工具是最好的追踪方式。
在一个页面元素上点击鼠标右键,选择弹出菜单上的检查元素,就能打开开发者工具,示例如下所示。

使用开发者工具检测CSS

样式检查器显示了所检查元素的每个选择器,它们根据优先级排列。
在选择器下方是继承属性。元素所有的层叠和继承一目了然。

有很多细节可以帮助开发人员弄清楚一个元素的样式是怎么产生的。
靠近顶部的样式会覆盖下面的样式。被覆盖的样式上划了删除线。
右侧显示了每个规则集的样式表和行号,你可以在源代码中找到它们。
这样就能准确判断哪个元素继承了哪些样式以及这些样式的来源。还可以在顶部的筛选框中选择特定的声明,同时隐藏其他声明。

继承示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Inheritance In Action</title>
<style>
body {
  background-color: rgba(87,185,178,.5);
}
p {
  color: rgb(50,122,167);
  padding-left: 20px;
  border-left: solid 25px rgba(255,255,255,.5);
}
.content {
  font-family: "Helvetica Neue", Arial, Helvetica, sans-serif;
  font-size: 18px;
  color: rgb(194,91,116);
  max-width: 900px;
  margin: 0 auto;
}
</style>
</head>
<body class="content">
  <h1>The Amazing World of CSS</h1>
  <p><strong>Sed ut perspiciatis</strong> unde omnis iste natus error sit <em>voluptatem accusantium</em> doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo <strong>inventore veritatis et quasi architecto beatae</strong> vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia  eos qui ratione voluptatem sequi nesciunt. Learn more about CSS at the <a href="http://www.w3.org/Style/CSS/">W3C CSS Home Page</a>.</p>
  <ul>
    <li>adipisci velit</li>
    <li>autem vel eum iure re</li>
    <li> ut lautem vel eum i</li>
  </ul>
  <h2>Who Knew CSS Had Such Power?</h2>
  <p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem <strong>Sed ut perspiciatis</strong> unde omnis iste natus error sit <em>voluptatem accusantium</em> doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo <em>inventore veritatis et quasi architecto</em> beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia  eos qui ratione voluptatem sequi nesciunt. Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
  <h3>Not Me!</h3>
  <p><strong>Sed ut perspiciatis</strong> unde omnis iste natus error sit <em>voluptatem accusantium</em> doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia  eos qui ratione voluptatem sequi nesciunt. odi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima <strong>veniam, quis nostrum</strong> exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
  <h3>Me Neither!</h3>
  <p><strong>Sed ut perspiciatis</strong> unde omnis iste natus error sit <em>voluptatem accusantium</em> doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia  eos qui ratione voluptatem sequi nesciunt. </p>
</body>
</html>

这个网页的外观整体看起来很统一,不过仔细观察,你会发现类样式中设置的颜色只影响了标题和无序列表;而且,即便设置了字号,标题文字的大小还是与段落不同。

我们在每个段落的左侧都添加了边框,而且把文字向右移了一点,防止文字与边框连在一起。
注意,所有<p>标签的左侧都有一个淡色粗边框。可是,<p>标签里的标签(例如<em>标签)没有缩进,也没有边框。
这种行为是合理的,如果段落里每个<em><strong>标签都有粗边框,而且边框之间有 20 像素间隔,那就太奇怪了。

继承示例最终效果

层叠

CSS 本质上就是声明规则,即在各种条件下,我们希望产生特定的效果。如果某个元素有这个类,则应用这些样式。
如果 X 元素是 Y 元素的子节点,则应用那些样式。浏览器会根据这些规则,判断每个规则应该用在哪里,并使用它们去渲染页面。

如果只看几个小例子,CSS 的规则很容易理解。
但是当样式表变大,或者将同一份样式表应用到更多的网页时,CSS 代码很快就会变得复杂。在 CSS 里实现一个效果通常有好几种方式。
当 HTML 结构变化,或者将同一份样式表应用到不同的网页时,不同的实现方式会产生不同的结果。

样式冲突时会出现一些特别怪异的事情,比如说,明明在类样式中把文字设为红色了,可以最终显示的却是天蓝色。
幸好,CSS 会使用一种机制处理冲突。这种机制叫层叠(cascade),用于管控样式之间相互作用的方式,出现冲突时判定哪个样式的优先级高。

CSS 开发很重要的一点就是以可预测的方式书写规则。

首先我们需要理解浏览器如何解析样式规则。每条规则单独来看很简单,但是当两条规则提供了冲突的样式时会发生什么呢?

如果你发现有一条规则没有按照预期生效,可能是因为另一条规则跟它冲突了。
要想预测规则最终的效果,就需要理解 CSS 里的层叠。

为了演示,你需要构建一个简单的网页头部,如下图所示。上面是网站标题,下面是一排蓝绿色的导航链接。
最后一个链接是橘黄色的,用来表示其特殊性。

网页标题和导航链接

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<!doctype html>
<head>
  <style>
h1 {
  font-family: serif;
}

#page-title {
  font-family: sans-serif;
}

.title {
  font-family: monospace;
}
  </style>
</head>
<body>
  <header class="page-header">
    <h1 id="page-title" class="title">Wombat Coffee Roasters</h1>
    <nav>
      <ul id="main-nav" class="nav">
        <li><a href="/">Home</a></li>
        <li><a href="/coffees">Coffees</a></li>
        <li><a href="/brewers">Brewers</a></li>
        <li><a href="/specials" class="featured">Specials</a></li>
      </ul>
    </nav>
  </header>
</body>

对同一个元素应用多个规则时,规则中可能会包含冲突的声明。上面的代码就展示了这一点。
它包含三个规则集,每一个给网页标题指定了不同的字体样式。标题不可能同时显示三种样式。哪一个会生效呢?

浏览器为了解决这个问题会遵循一系列规则,因此最终的效果可以预测。在上面的例子里,规则决定了第二个声明(即 ID 选择器)生效,因此标题采用 sans-serif 字体

ID选择器生效后的网页标题

层叠指的就是这一系列规则。它决定了如何解决冲突,是 CSS 语言的基础。虽然有经验的开发人员对层叠有大体的了解,但是层叠里有些规则还是容易让人误解。

下面来分析层叠的规则。当声明冲突时,层叠会依据三种条件解决冲突。

(1) 样式表的来源: 样式是从哪里来的,包括你的样式和浏览器默认样式等。
(2) 选择器优先级: 哪些选择器比另一些选择器更重要。
(3) 源码顺序: 样式在样式表里的声明顺序

层叠的规则是按照这种顺序来考虑的。下图概括展示了规则的用法

层叠的高级流程图

这些规则让浏览器以可预测的方式解决 CSS 样式规则的冲突。

术语解释:
以下是 CSS 中的一行。他被称作一个声明。该声明由一个属性(color)和一个值(black)组成:

1
color: black;

不要将 CSS 属性(property) 跟 HTML 属性(attribute)混淆。比如在 <a href="/"> 元素里,href 就是 a 标签的一个 HTML 属性。

包含在大括号内的一组声明被称作一个声明块。声明块前面有一个选择器(如下面的 body)。

1
2
3
4
body {
    color: black;
    font-family: Helvetica;
}

选择器和声明块一起组成了规则集(ruleset)。
一个规则集也简称一个规则,不过我发现很少有人说单数形式的规则(rule),通常会用复数形式(rules),用来指一系列样式的几何。

最后,@规则(at-rules)是指用 "@" 符号开头的语法。
比如 @import 规则或者 @media 查询

样式表的来源

你添加到网页里的样式表并不是浏览器唯一使用的样式表,还有其他类型或来源的样式表。
你的样式表属于作者样式表,除此之外还有用户代理样式表,即浏览器默认样式。
用户代理样式表优先级低,你的样式会覆盖它们。

用户代理样式在不同浏览器上稍有差异,但是大体上是在做相同的事情: 为标题 <h1><h6> 和段落 <p> 添加上下外边距,为列表 <ol><ul> 添加左侧内边距,为链接添加颜色,为元素设置各种默认字号。

用户代理样式

再看一下示例的网页。标题字体是 sans-serif,由你添加的样式决定。
其他元素的样式则是由用户代理样式决定: 列表有左侧内边距,list-style-typedisc,因此有项目符号(小黑点)。
链接为蓝色且有下划线。标题和列表有上下外边距。

ID选择器生效后的网页标题

浏览器应用了用户代理样式后才会应用你的样式表,即作者样式表。
你指定的声明会覆盖用户代理样式表里的样式。如果你在 HTML 里面链接了多个样式表,那么它们的来源都相同,即作者。

用户代理样式表因为设置了用户普遍需要的样式,所以不会做一些完全超出预期的事情。
当你不喜欢默认样式时,可以在自己的样式表里设置别的值。
现在就试试覆盖一些你不想要的用户代理的样式,让网页看起来如下图所示。

作者样式覆盖代理样式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!doctype html>
<head>
  <style>
/* 减小外边距,删除用户代理的列表样式 */
h1 {
  color: #2f4f4f;
  margin-bottom: 10px;
}

#main-nav {
  margin-top: 10px;
  list-style: none;
  padding-left: 0;
}

/* 让列表元素水平排列,而不是上下叠放 */
#main-nav li {
  display: inline-block;
}

/* 给导航链接加上类似按钮的外观 */
#main-nav a {
  color: white;
  background-color: #13a4a4;
  padding: 5px;
  border-radius: 2px;
  text-decoration: none;
}
  </style>
</head>
<body>
  <header class="page-header">
    <h1 id="page-title" class="title">Wombat Coffee Roasters</h1>
    <nav>
      <ul id="main-nav" class="nav">
        <li><a href="/">Home</a></li>
        <li><a href="/coffees">Coffees</a></li>
        <li><a href="/brewers">Brewers</a></li>
        <li><a href="/specials" class="featured">Specials</a></li>
      </ul>
    </nav>
  </header>
</body>

如果长期使用 CSS,你大概习惯了覆盖用户代理的样式。
这种做法实际上就是利用了层叠的样式来源不同,你写的样式会覆盖用户代理样式,因为来源不同。

说明: 上面用了 ID 选择器,但应该避免使用这种选择器

!important 声明

样式来源规则有一个例外: 标记为重要(important) 的声明。
如下所示,在声明的后面、分号的前面加上 !important,该声明就会被标记为重要的声明。

1
color: red !important;

标记了 !important 的声明会被当作更高优先级的来源,因此总体的优先级按照由高到低排列如下所示:

(1) 作者的 !important
(2) 作者
(3) 用户代理

层叠独立地解决了网页中每个元素的样式属性的冲突。
例如,如果给段落设置加粗的字体,用户代理的上下外边距样式仍然会生效(除非被明确覆盖)。
处理过渡和动画时,还会再提到样式来源的概念,因为它们会引入更多的来源。
!important 注释是 CSS 的一个有趣而怪异的特性。

理解优先级

如果无法用来源解决冲突声明,浏览器会尝试检查它们的优先级
理解优先级很重要。不理解样式的来源照样可以写 CSS,因为 99% 的网站样式是来自同样的源。
但是如果不理解优先级,就会被坑得很惨。不幸的是,很少有人提及这个概念。

浏览器将优先级分为两部分: HTML 的行内样式和选择器的样式。

行内样式:

如果用 HTML 的 style 属性写样式,这个声明只会作用于当前元素。
实际上行内元素属于“带作用域的”声明,它会覆盖任何来自样式表或者 <style> 标签的样式。
行内样式没有选择器,因为它们直接作用于所在的元素。

在示例中,需要让导航菜单里的特殊链接变成橘黄色,有好几种方式能够实现这种效果。

网页标题和导航链接

首先使用行内样式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
<body>
  <header class="page-header">
    <h1 id="page-title" class="title">Wombat Coffee Roasters</h1>
    <nav>
      <ul id="main-nav" class="nav">
        <li><a href="/">Home</a></li>
        <li><a href="/coffees">Coffees</a></li>
        <li><a href="/brewers">Brewers</a></li>
        <li><a href="/specials" class="featured" style="background-color: orange;">Specials</a></li>
      </ul>
    </nav>
  </header>
</body>

如果要在样式表里覆盖行内声明,需要为声明添加 !important,这样能将它提升到一个更高优先级的来源。
但如果行内样式也被标记为 !important,就无法覆盖它了。最好是只在样式表内用 !important

选择器优先级

优先级的第二部分由选择器决定。比如,有两个类名的选择器比只有一个类名的选择器优先级更高。
如果一个声明将背景色设置为橘黄色,但另一个更高优先级的声明将其设置为蓝绿色,浏览器就会将蓝绿色应用到元素上。

为了演示,我们尝试用一个简单的类选择器将特殊链接设置为橘黄色。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#main-nav a {
  color: white;
  background-color: #13a4a4;
  padding: 5px;
  border-radius: 2px;
  text-decoration: none;
}

.featured {
  background-color: orange;
}

没有生效!所有的链接仍然是蓝绿色。为什么呢?第一个选择器的优先级高于第二个选择器。
第一个由一个 ID 和一个标签名组成,而第二个由一个类名组成。但选择器的长度并不是决定优先级的唯一因素。

不同类型的选择器有不同的优先级。比如,ID 选择器比类选择器优先级更高。
实际上,ID 选择器的优先级比拥有任意多个类的选择器都高。
同理,类选择器的优先级比标签选择器(也称类型选择器)更高。

优先级的准确规则如下:

  • 如果选择器的 ID 数量更多,则它会胜出(即它更明确)
  • 如果 ID 数量一致,那么拥有最多类的选择器胜出
  • 如果以上两次比较都一致,那么拥有最多标签名的选择器胜出

按照优先级由低到高排列的选择器:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
/* (1) 4 个标签 */
html body header h1 {
    color: blue;
}

/* (2) 3 个标签和 1 个类 */
body header.page-header h1 {
    color: orange;
}

/* (3) 2 个类 */
.page-header .title {
    color: green;
}

/* (4) 1 个 ID */
#page-title {
    color: red;
}

最明确的选择器是有 1 个 ID 的(4),因此标题的颜色最终为红色。第二明确的是有 2 个类的(3)。如果没有出现带 ID 选择器的 (4),则 (3) 的声明会生效。选择器(3)比选择器 (2) 的优先级更高,尽管选择器 (2) 更长: 2 个类比 1 个类更明确。
最后,选择器 (1) 最不明确,它有 4 个元素类型(即标签名),但是没有 ID 或者类

说明:
伪类选择器(如: hover)和属性选择器(如[type="input"]) 于一个类选择器的优先级相同。
通用选择器(*)和组合器(>、+、~)对优先级没有影响。

如果你在 CSS 里写了一个声明,但是没有生效,一般是因为被更高优先级的规则覆盖了。
很多时候开发人员使用 ID 选择器,却不知道它会创建更高的优先级,之后就很难覆盖它。如果要覆盖一个 ID 选择器的样式,就必须要用另一个 ID 选择器。

这个概念很简单,但是如果你不理解优先级,就无法弄清楚为什么一饿规则能生效,另一个却不能。

优先级标记

一个常用的表示优先级的方式是用数值形式来标记,通常用逗号隔开每个数。比如,"1,2,2" 表示选择器由 1 个 ID、2 个类、2 个标签组合。
优先级最高的 ID 列为第一位,紧接着是类,最后是标签。

选择器 #page-header #page-title 有 2 个 ID,没有类,也没有标签,它的优先级可以用 "2,0,0" 表示。
选择器 ui li 有 2 个标签,没有 ID,也没有类名,它的优先级可以用 "0,0,2" 表示。

有时,人们还会用 4 个数的标记,其中将最重要的位置用 0 或 1 来表示,代表一个声明是否是用行内样式添加的。
此时,行内样式的优先级为 "1,0,0,0"。
它会覆盖通过选择器添加的样式,比如优先级为 "0,1,2,0"(1 个 ID 和 2 个类)的选择器

关于优先级的思考

之前常使用 .feature 选择器添加橘黄色背景,但是没有成功。
#main-nav a 选择器包含了一个 ID,覆盖了类选择器(优先级分别为 "1,0,1" 和 "0,1,0")。
有好几种方法可以解决这个问题。下面介绍几种可行的方法。

最快的方法是将 !important 添加到想要设置的元素的声明上。

1
2
3
.featured {
  background-color: orange !important;
}

这个方法之所以生效,是因为 !important 注释将声明提升到了更高优先级的来源。这个方法的确简单,但也很低级。
它可能解决了眼前的问题,但是会在以后带来更多问题。
一旦给很多声明加上 !important,要覆盖已设置为 important 的声明时,该怎么做呢?
当给一些声明加上 !important 时,就会先比较来源,再使用常规的优先级规则。
最终会让一切回到起点: 一旦引入一个 !important,就会带来更多的 !important

那么更好的方法是什么?请不要试图绕开选择器优先级,而是利用它来解决问题。
何不提升选择器的优先级呢?将你的 CSS 修改为下面的代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* 优先级仍然是 "1,0,1" */
#main-nav a {
  color: white;
  background-color: #13a4a4;
  padding: 5px;
  border-radius: 2px;
  text-decoration: none;
}
/* 将优先级提升到 "1,1,0" */
#main-nav .featured {
  background-color: orange;
}

这个方法也奏效了。现在你的选择器有一个 ID 和 1 个类,优先级为 “1,1,0”,比 #main-nav a(优先级为 "1,0,1")高。
因此,橘黄色背景是能够应用到元素上的。

但是这个方法还能改进。不提升第二个选择器的优先级,而是降低第一个选择器的优先级。
导航链接元素同时还有一个类: <ul id="main-nav" class="nav">
所以可以修改 CSS,通过类名而不是 ID 来选中元素。将选择器里的 #main-nav 改为 .nav

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.nav {
  margin-top: 10px;
  list-style: none;
  padding-left: 0;
}

.nav li {
  display: inline-block;
}

/* 降低第一个选择器的优先级 (0,1,1) */
.nav a {
  color: white;
  background-color: #13a4a4;
  padding: 5px;
  border-radius: 2px;
  text-decoration: none;
}

/* 提升第二个选择器的优先级 (0,2,0) */
.nav .featured {
  background-color: orange;
}

现在你已经降低了这些选择器的优先级。橘黄色背景的优先级足够高,能够覆盖蓝绿色

通过这些例子可以发现,优先级容易发展为一种“军备竞赛”。在大型项目中这一点尤为突出。
通常最好让优先级尽可能低,这样当需要覆盖一些样式时,才能有选择空间。

源码顺序

层叠的第三步,也是最后一步,是源码顺序。
如果两个声明的来源和优先级相同,其中一个声明在样式表中出现较晚,或者位于页面较晚引入的样式表中,则该声明胜出。

也就是说,可以通过控制源码顺序,来给特殊链接添加样式。
如果两个冲突选择器的优先级相同,则出现较晚的那个胜出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
/* 让优先级相同(0,1,1) */
.nav a {
  color: white;
  background-color: #13a4a4;
  padding: 5px;
  border-radius: 2px;
  text-decoration: none;
}

a.featured {
  background-color: orange;
}

在这个方法里,选择器优先级相同。
源码顺序决定了哪个声明作用域特殊链接,最终产生了橘黄色的特殊按钮

这个方法解决了问题,但也引入了一个潜在的新问题: 虽然在 nav 元素里的特殊按钮看起来正常了,但是如果你想要在页面其他地方,在 nav 之外的链接上使用 featured 类呢?
最后就会有奇怪的混合样式: 橘黄色的背景,但是导航链接没有文本颜色、内边距或者圆角效果。

位于nav声明之外的featured类产生了奇怪的效果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<body>
  <header class="page-header">
    <h1 id="page-title" class="title">Wombat Coffee Roasters</h1>
    <nav>
      <ul id="main-nav" class="nav">
        <li><a href="/">Home</a></li>
        <li><a href="/coffees">Coffees</a></li>
        <li><a href="/brewers">Brewers</a></li>
        <li><a href="/specials" class="featured">Specials</a></li>
      </ul>
    </nav>
  </header>
  <main>
    <p>
      Be sure to check out
      <!-- nav 之外的特殊链接只会有部分样式 -->
      <a href="/specials" class="featured">our specials</a>.
    </p>
  </main>
</body>

有一个元素只被第二个选择器选中,没有被第一个选中,因而没有产生期望的结果。
你得决定是否让 nav 以外的元素拥有橘黄色的按钮样式,如果是,需要确保将所有想要的样式都应用到元素上。

除非网站有其他需求,否则我倾向于方法三。理想状态下,你可以凭经验判断在页面其他地方会出现什么样式需求。
也许你知道别处也可能想要一个特殊链接,这种情况下,也许方法四更合适,当然在别处还需要添加一些样式来补充 feature 类。

正如之前所说,在 CSS 中最好的答案通常是“这得看情况”。实现相同的效果有很多途径。
多想些实现方法,并思考每一种方法的利弊,这是很有价值的。面对一个样式问题时,我经常分两个步骤来解决它。
首先确定哪些声明可以实现效果。其次,思考可以用哪些选择器结构,然后选择最符合需求的那个。

链接样式和源码顺序

你刚开始学习 CSS 时,或许就知道给链接加样式要按照一定的顺序书写选择器。这是因为源码顺序影响了层叠。
下面代码展示了如何以 "正确" 的顺序书写链接样式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<!doctype html>
<head>
  <style>
a:link {
  color: blue;
  text-decoration: none;
}

a:visited {
  color: purple;
}

a:hover {
  text-decoration: underline;
}

a:active {
  color: red;
}

h1 {
  color: #2f4f4f;
  margin-bottom: 10px;
}

.nav {
  margin-top: 10px;
  list-style: none;
  padding-left: 0;
}

.nav li {
  display: inline-block;
}

.nav a {
  color: white;
  background-color: #13a4a4;
  padding: 5px;
  border-radius: 2px;
  text-decoration: none;
}

.nav .featured {
  background-color: orange;
}
  </style>
</head>
<body>
  <header class="page-header">
    <h1 id="page-title" class="title">Wombat Coffee Roasters</h1>
    <nav>
      <ul id="main-nav" class="nav">
        <li><a href="/">Home</a></li>
        <li><a href="/coffees">Coffees</a></li>
        <li><a href="/brewers">Brewers</a></li>
        <li><a href="/specials" class="featured">Specials</a></li>
      </ul>
    </nav>
  </header>
</body>

书写顺序之所以很重要,是因为层叠。优先级相同时,后出现的样式会覆盖先出现的样式。
如果一个元素同时处于两个或者更多状态,最后一个状态就能覆盖其他状态。
如果用户将鼠标悬停在一个访问过的链接上,悬停效果会生效。如果用户在鼠标悬停时激活了链接(即点击了它),激活的样式会生效。

这个顺序的记忆口诀是 "LoVe/HAte"("爱/恨",即 link(链接)、visited(访问)、hover(悬停)、active(激活)。
注意,如果将一个选择器的优先级改得跟其他的选择器不一样,这个规则就会遭到破坏,可能会带来意想不到的结果。

层叠值

浏览器遵循三个步骤,即来源、优先级、源码顺序,来解析网页上每个元素的每个属性。
如果一个声明在层叠中“胜出”,它就被称作一个层叠值。元素的每个属性最多只有一个层叠值。
网页上一个特定的段落(<p>)可以有一个上外边距和一个下外边距,但是不能有两个不同的上外边距或两个不同的下外边距。
如果 CSS 为同一个属性指定了不同的值,层叠最终会选择一个值来渲染元素,这就是层叠值。

层叠值: 作为层叠结果,应用到一个元素上的特定属性的值

如果一个元素上始终没有指定一个属性,这个属性就没有层叠值。
还是拿段落举例,可能就没有指定的边框或者内边距。

两条经验法则

你可能知道,处理层叠时有两条通用的经验法则。因为它们很有用,所以提一下。

(1) 在选择器中不要使用ID。就算只用一个 ID,也会大幅提升优先级。当需要覆盖这个选择器时,通常找不到另一个有意义的 ID,于是就会复制原来的选择器,然后加上另一个类,让它区别于想要覆盖的选择器。

(2) 不要使用 !important。它比 ID 更难覆盖,一旦用了它,想要覆盖原先的声明,就需要再加上一个 !important,而且依然要处理优先级的问题。

这两条规则是很好的建议,但不必固守它们,因为也有例外。不要为了赢得优先级竞赛而习惯性地使用这两个方法

关于重要性的一个重要提醒:

当创建一个用于分发的 JavaScript 模块(比如 NPM包) 时,强烈建议尽量不要在 JavaScript 里使用行内样式。
如果这样做了,就是在强迫使用该包的开发人员要么全盘接受包里的样式,要么给每个想修改的属性加上 !important

正确的做法是在包里包含一个样式表。
如果组件需要频繁修改样式,通常最好用 JavaScript 给元素添加或者移除类。
这样用户就可以在使用这份样式表的同时,在不引入优先级竞赛的前提下,按照自己的喜好选择编辑其中的样式。

过去几年涌现了一些实践方法,能够帮助我们管理选择器选择器。
包括如何处理优先级,以及在哪里可以放心使用!important

样式层叠的方式

层叠是指一系列规则,用于确定把哪些样式属性应用到元素上。
Web 浏览器通过层叠规则判断如何处理应用于同一个标签上的多个样式,确定如何处理有冲突的 CSS 属性。
以下两种情况会导致样式冲突: 继承,从多个祖辈那里继承相同的属性;
同一个元素有多个样式(例如为某个段落定义了类样式,又定义了<p>标签样式,此时这两个样式都会应用到那个段落上)

多个继承的样式和专为标签定义的样式顺利地组合在一起,形成了混合样式。可以,如果继承的 CSS 属性有冲突怎么办?
假如我们把<body>标签的文字样色设置为红色,把<p>标签的文字颜色设为绿色。
如果段落里有个<strong>标签,这个标签会从<body><p>标签那里继承样式,那么<strong>标签里的文字会显示成红色还是绿色呢?
女士们先生们,胜出的是 -- 段落样式里设定的绿色。这是因为 Web 浏览器会采用离标签最近的祖辈的样式

在这个例子中,从<body>标签继承的属性更具一般性,对所有的标签都有效。而应用在你<p>标签上的样式更具针对性,只对<p>标签和内部的标签有效

简单来说,如果没有专门为标签定义样式,继承的属性出现冲突时,最近的祖辈胜出

再举一个例子。如果某个 CSS 样式把<table>标签里的文字定义为一个颜色,另一个样式把<td>标签里的文字定义为其他颜色,
那么单元格(<td>标签)里的标签,例如段落、标题和无序列表,都会使用为<td>标签定义的文字颜色,因为<td>标签是最近的祖辈

由“最近的祖辈胜出”规则可以推理出,在 CSS 继承体系中有一种样式始终会胜出--直接应用在标签上的样式。
假如<body><p><strong>标签都设置了颜色,<p>标签样式比<body>标签样式具体,<strong>标签样式则比<body><p>标签样式都具体。
<strong>标签样式只应用在<strong>标签上,而且会覆盖从其他标签继承的有冲突的属性。也就是说,标签专用样式里的属性会击败所有继承的属性。

这个规则解释了为什么有些继承的属性看起来像没继承一样,例如,段落的文字是红色,而链接的颜色仍然是浏览器默认的蓝色。
这是因为浏览器为<a>标签预设了样式,所以链接不会使用继承的文本颜色。

一个标签,多个样式

继承是多个样式影响一个标签的方式之一。除此之外,还可以把多个样式直接应用到一个标签上。
例如,一个外部样式表为<p>标签定义了样式,链接这个外部样式表的网页中还有内部样式表,而且内部样式表也为<p>标签定义了样式。
更有趣的是,这个页面中有个<p>标签应用了类样式。那么,现在有三个样式直接应用在这个<p>标签上。遇到这种情况时,浏览器会采用哪个(或哪些)样式呢?

答案是,视情况而定。根据样式的类型,以及定义各个样式的顺序,浏览器一次可能会使用一个或多个样式。下面列出一次使用多个样式的几种情况:

  • 使用标签选择符定义了样式,又把类样式应用到标签上。例如,为<h2>标签定义了标签样式,又使用下述 HTML 代码把 .leadHeadline 类样式应用到这个标签上: <h2 class="leadHeadline">Your Future Revealed!</h2>,此时,两个样式都会应用到这个 <h2> 标签上
  • 相同的样式名在样式表中出现多次。例如,样式表中有一个群组选择符.leadHeadline, .secondaryHeadline, .newsHeadline,还有一个类样式.leadHeadline。 这两个规则定义的样式都会应用到 class 属性的值中包括 leadHeadline 的元素上
  • 既有类样式,又有 ID 样式的标签。例如,ID 为 #banner,类为.news,HTML 代码为<div id="banner" class="news">。此时,#banner样式和.news样式都会应用到<div>标签上
  • 一个网页用到了多个样式表,而且各个样式表中都有名称相同的样式。这些名称相同的样式可以在外部样式表中,也可以在内部样式表中。或者在网页链接的多个外部样式表中
  • 多个复杂的选择符选取相同的标签。使用后代选择符时时经常会出现这种情况。比如说网页中有个<div>标签(例如<div id="mainContent">),这个<div>标签里有个段落,而且这个段落设置了类: <p class="byline">
    那么,下述选择符都能选取这个段落: css #mainContent p #mainContent .byline p.byline .byline

如果一个元素有多个样式,Web 浏览器会合并这些样式的属性,不过前提是,样式之间没有冲突。下面举个例子,说明这个规则。
假设网页中有一段文字的内容是作者名,而且链接到作者的电子邮件地址。这个段落的 HTML 代码可能是下面这样:

1
<p class="byline">Written by <a href="mailto:jean@cosmofarmer.com">Jean Graine de Pomme</a></p>

而这个网页的样式表中有三个用于装饰链接外观的样式:

1
2
3
4
5
6
7
8
9
a {
  color: #6378df;
}
p a {
  font-weight: bold;
}
.byline a {
  text-decoration: none;
}

第一个样式把所有<a>标签的颜色都设为深蓝色,第二个样式让<p>标签里的<a>标签都以粗体显示,第三个样式把类为byline的元素中链接的下划线去掉。
这三个样式都会应用到那个电子邮件地址链接上。因为这些样式中没有相同的属性,所以不会冲突。

特指度:确定哪个样式胜出

前面那个示例特别易于理解。但是,如果那三个链接样式设置了不同的 font-family 属性呢?Web 浏览器会使用哪个样式里设定的字体呢?

如果你仔细阅读了前文,会发现,层叠提供了一套帮助浏览器解决属性冲突的规则,即最具体的样式里的属性胜出。可是,就像前面那几个样式一样,有时无法确定哪个样式更具体。
幸好,CSS 提供了一个公式,根据赋给各种选择符(标签选择符,类选择符,ID 选择符)的值计算样式的特指度(specificity)。赋给各种选择符的值如下:

  • 一个标签选择符记 1 分
  • 一个类选择符记 10 分
  • 一个 ID 选择符记 100 分
  • 一个行内样式记 1000 分

注意: 计算特指度的方式比这里所述的要复杂一点,不过除了最诡异的情况之外,都适用这个公式。

计算得到的值越大,特指度越高。假如我们编写了下述三个样式:

  • <img>标签定义一个标签样式(特指度: 1)
  • 一个名为.highlight的类样式(特指度: 10)
  • 一个名为#logo的 ID 样式(特指度: 100)

假如网页中有这行 HTML 代码: <img id="logo" class="highlight" src="logo.gif" />
如果在三个样式中都定义了相同的属性,例如 border 属性,那么,ID 样式(#logo)里的值胜出

注意: 计算特指度时,伪元素(如::first-line)与标签选择符一样,记 1 分;伪类(如:link)与类选择符一样,记 10 分

后代选择符由多个选择符组成(如#content ph2 strong),计算方式要复杂些。后代选择符的特指度是其中各个选择符的得分总和。

图5-3多个样式冲突时的得分

注意: 继承的属性没有特指度。所以,即使一个标签从特指度高的样式(如#banner)中继承了属性,继承的属性也会被直接应用在标签上的样式覆盖。

特指度相同时后一个样式胜出

属性有冲突的两个样式可能有相同的特指度。
如果两个地方使用相同的选择符,特指度会打成平局,例如,内部样式表和外部样式表中都有 p 标签选择器,此时特指度就一样。
当然,两个不同的样式也可能有相同的特指度。特指度一样时,样式表中后面出现的样式胜出。

我们以下述 HTML 代码举个棘手的例子:

1
<p class="byline">Written by <a class="email" href="mailto:jean@cosmofarmer.com">Jean Graine de Pomme</a></p>

包含上述段落和链接的网页,其样式表中有下面两个样式:

1
2
3
4
5
6
p .email {
  color: blue;
}
.byline a {
  color: red;
}

这两个样式的特指度都是 11(类选择符 10 分,标签选择符 1 分),而且都应用在那个<a>标签上。因此,这两个样式的特指度打成了平局。
那么,浏览器使用哪个颜色装饰上述段落里的链接呢?答案是,红色,因为设定红色的那个属性在第二个样式中(即在后面的样式中)

现在,调换两个样式的位置,把样式包改成下面这样:

1
2
3
4
5
6
.byline a {
  color: red;
}
p .email {
  color: blue;
}

此时,链接的颜色会变成蓝色。因为p .email样式在.byline a样式之后,所以p .email样式里的属性胜出

如果外部样式表里的规则与内部样式表里的规则冲突了呢?遇到这种情况时,样式表(在HTML文件里)的位置特别重要。
如果先使用<style>标签添加内部样式表,而后再使用<link>标签链接外部样式表,那么,外部样式表里的样式胜出(即后面的样式胜出)。
我们要尽量把外部样式放在固定的位置,而且最好先链接外部样式表。仅当需要在单个页面中添加样式时才使用内部样式表。

控制层叠

可以看出,CSS 样式变多之后,格式变乱的可能性会随之增大。比如说我们编写了一个类样式,指定字体和字号,可是应用到段落上之后,没有任何效果。
这种问题通常与层叠有关。你可能以为在标签上设置了类就会应用类样式里的格式属性,然而,如果有特指度更高的样式,情况可能就不一样了。

这种问题的处理方式有好几种。其一,可以使用!important,确保属性始终有效。可以这种方法有点笨拙,因为无法预知什么时候要压制这种效果。除此之外还有两种方法

  • 改变特指度
  • 有选择地覆盖:
    我们还可以有选择地覆盖样式,精确调整特定页面的外观设计。比如说,我们编写了一个名为styles.css的外部样式表,应用于网站里的每个页面。
    这个样式表中定义的是网站的整体外观,包括<h1>标签的字体和颜色,表单元素的外观等。
    可是,我们或许想让首页的<h1>标签与众不同,例如更粗更大,可能还想把段落里的文字变得更小,以便显示更多的信息。
    也就是说,我们仍然想使用styles.css文件里的大多数样式,只不过想覆盖几个标签(如<h1><p>等)的部分属性。
    这种需求的一种处理方式是使用内部样式表,列出想覆盖的样式。假如styles.css文件里有如下规则:
    css h1 { font-family: Arial, Helvetica, sans-serif; font-size: 24px; color: #000; }
    如果想让首页里的<h1>标签字号变大,显示成红色,在首页的内部样式表中添加如下样式即可:
    css h1 { font-size: 36px; color: red; } 这样,首页里的<h1>标签会使用Arial字体(根据外部样式表里的样式),不过颜色变成了红色,字号变成了 36 像素(根据内部样式表里的样式)。
    提示: 在 HTML 代码的<head>部分里要先链接外部样式表,再添加内部样式表。这样,样式的特指度相同,内部样式表里的样式才会胜出
    另一种处理方式是,再编写一个外部样式表,例如命名为home.css。首先除了链接styles.css样式表之外,再链接这个样式表。
    home.css文件中保存的样式名和属性用于覆盖styles.css文件里相应的样式和属性。为了让这种方式生效,要在styles.css文件之后链接home.css文件,如下所示:
    html <link rel="stylesheet" href="css/styles.css" /> <link rel="stylesheet" href="css/home.css" />

提示: 还有一种方式能精确调整各个页面的外观: 在不同的页面中为<body>标签设定不同的类,例如.review.story.home,然后使用后代选择符修改各个页面中标签的外观。

避免特指度战争:

如今,很多 Web 设计师都避免使用 ID,而偏爱使用类。其中一个原因是,ID 选择符的特指度太高,不易覆盖。
修改特指度往往会导致特指度战争,致使样式表中充斥着特别长特别复杂的选择符。这种问题最好通过实例说明。假如网页中有如下 HTML 代码片段:

1
2
3
4
5
<div id="article">
  <p>A paragraph</p>
  <p>Another paragraph</p>
  <p class="special">A special paragraph</p>
</div>

我们想让这个<div>标签里的文字使用红色显示,所以使用后代选择符定义了一个样式,如下所示:

1
2
3
#article p {
  color: red;
}

可是,我们想让类为 special 的那个段落使用蓝色显示。如果仅仅使用类选择符定义样式,无法实现所需的效果:

1
2
3
.special {
  color: blue;
}

我们必须把前面那个类选择符改成下面这样才行:

1
2
3
#article .special {
  color: blue;
}

可是,这样修改又带来了两个问题: 其一,选择符变长了;其二,仅当 special 类在 ID 为 article 的元素中才会使用蓝色显示。
例如,把<p class="special">A special paragraph</p>复制粘贴到页面的其他部分,就不会使用蓝色显示。
也就是说,使用 ID 会让选择符变得更长,而作用却降低了。

现在我们来看一下把 ID 改成类之后情况如何。修改之后,前面那段 HTML 代码变成了:

1
2
3
4
5
<div class="article">
  <p>A paragraph</p>
  <p>Another paragraph</p>
  <p class="special">A special paragraph</p>
</div>

此时,可以把 CSS 改成:

1
2
3
4
5
6
.article p {
  color: red;
}
p.special {
  color: blue;
}

第一个样式使用的是后代选择符.article p,其特指度为 11.第二个样式使用的选择符是 p.special,特指度也是 11(一个标签,一个类)。
不过第二个选择符的意思是,"把样式里的属性应用到类为 special 的任何段落上。"现在,把那个段落复制粘贴到网页的任何地方,都会使用蓝色显示。

这只是一个示例,在样式表中不难发现有些选择符长到难以置信,例如#home #article #sidebar #legal p#home #article #sidebar #legal p.special

当然,ID 也有用处。例如,很多 CMS(Content Management System,内容管理系统)会使用 ID 标识页面中独一无二的元素,此时就适合使用 ID 选择符。
ID 选择符的特指度高,所以便于覆盖其他样式。不过,ID 选择符不能滥用。大多数情况下,ID 选择符都能替换为简单的类选择符或标签选择符。
ID 选择符的特指度太高,会把样式变得异常复杂。

重设默认样式:

浏览器为各种标签提供了默认的样式,例如: <h1>标签的字号比<h2>标签大,而且都是粗体;段落里的文字要小一些,而且不是粗体;
链接是蓝色的,有下划线;无序列表会向右缩进。HTML 标准没有定义任何格式,Web 浏览器提供这些格式是为了让基本的 HTML 更易于阅读。
虽然不同的浏览器为各种标签提供的样式大体相似,但是具体实现各不相同。

例如,Chrome 和 Firefox 使用 padding 属性缩进无序列表,而 Internet Explorer 则使用 margin 属性。除此之外,不同的浏览器为各个标签指定的字号会有细微的差别,而且大多数 Web 浏览器使用的外边距值也不相同。
浏览器之间的这种差异会导致一些问题,例如 Firefox 为某个元素添加了上外边距,而 Internet Explorer 则没有。出现这种问题,责任不在你,而是由浏览器内置样式之间的差异导致的。

为了避免浏览器之间的不一致性,编写样式表时最好统一标准。也就是说,我们要清除浏览器内置的格式,自行指定相应的格式。
清除浏览器内置样式这种行为称为 CSS 重置。接下来简述一种重置方式。

CSS 重置其实就是在样式表开头放一些核心标签的样式,在这些样式中设置在各种浏览器中表现不一致的属性,统一基准。

下面是一段简单的 CSS 重置代码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/* http://meyerweb.com/eric/tools/css/reset/ 
   v2.0 | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, 
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure, 
footer, header, hgroup, menu, nav, section {
    display: block;
}
body {
    line-height: 1;
}
ol, ul {
    list-style: none;
}
blockquote, q {
    quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
    content: '';
    content: none;
}
table {
    border-collapse: collapse;
    border-spacing: 0;
}

注意: 上述代码源自 Eric Meyer 制定的 CSS 重置代码。Eric 的 CSS 重置代码广为人知,影响深远,详情请访问: https://meyerweb.com/eric/tools/css/reset/

第一个样式使用一个特别长的群组选择符,把一些最常用的标签还原为本来的面貌--移除所有内外边距,把字号设为 100%,不使用粗体显示文本。
这么做是为了让常用的标签在各个浏览器中具有基本一致的外观,这正是 CSS 重置的意义所在: 统一基准,然后自己编写样式,让 HTML 代码在所有浏览器中都有一致的外观。

第二个样式使用的也是群组选择符article, aside, details,...,作用是让旧版浏览器正确地显示 HTML5 新引入的标签。
第三个样式(body)设置一致的 line-height 属性(即段落中行与行之间的间距)

除此之外,还可以使用 normalize.css 重置样式。这是一个免费开源的样式表,能让相同的标签在不同的浏览器中具有一致的外观,在 Web 设计圈子里使用广泛。
normalize.css 项目的网站地址是 http://necolas.github.io/normalize.css

第四个和第五个样式(ol 和 ul 标签样式)为列表设置统一的左边距和其他样式。最后一个样式的作用是便于为单元格添加边框

教程:层叠实战

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>CSS:The Missing Manual -- The Cascade</title>
<link href="styles.css" rel="stylesheet">
</head>
<body>
<h1>CSS: The Missing Manual</h1>
<div class="main">
  <p class="intro">Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. </p>
  <h2>Lorem Ipsum Dolor Sat</h2>
  <p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
  <h2>Nisi Ut Aliquid </h2>
  <p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
  <p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</div>
<div class="sidebar">
<h2>Sidebar</h2>
<p>Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur</p>
<p>adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?</p>
</div>
</body>
</html>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/* reset browser styles */
html, body, div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, a, abbr, acronym, address, big, cite, code, del, dfn, em, img, ins, kbd, q, s, samp,small, strike, strong, sub, sup, tt, var, b, u, i, center, dl, dt, dd, ol, ul, li, fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td, article, aside, canvas, details, embed, 
figure, figcaption, footer, header, hgroup, menu, nav, output, ruby, section, summary,
time, mark, audio, video {
    margin: 0;
    padding: 0;
    border: 0;
    font-size: 100%;
    font: inherit;
    vertical-align: baseline;
}
article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section {
    display: block;
}
body {
    line-height: 1.2;
}
ol { 
    padding-left: 1.4em;
    list-style: decimal;
}
ul {
    padding-left: 1.4em;
    list-style: square;
}
table {
    border-collapse: collapse;
    border-spacing: 0;
} 
/* end reset browser styles */

/* basic layout -- 2 columns */
.main {
    float: left;
    width: 70%;
}
.sidebar {
    margin-left: 75%;
    padding: 15px;
    background: #E9F5FE;
}
body {
  color: #B1967C;
  font-family: "Palatino Linotype", Baskerville, serif;
  padding-top: 115px;
  background: #CDE6FF url(images/bg_body.png) repeat-x;
  max-width: 800px;
  margin: 0 auto;
}
h1 {
  font-size: 3em;
  font-family: "Arial Black", Arial, sans-serif;
  margin-bottom: 15px;
}
h2 {
  font-size: 2.2em;
  color: #AFC3D6;
  margin-bottom: 5px;
}
.main h2 {
  color: #E8A064;
  border-bottom: 2px white solid;
  background: url(images/bullet_flower.png) no-repeat;
  padding: 0 0 2px 80px;
}
.main p {
  color: #616161;
  font-family: "Palatino Linotype", Baskerville, serif;
  font-size: 1.1em;
  line-height: 150%;
  margin-bottom: 10px;
  margin-left: 80px;
}
p.intro {
  color: #6A94CC;
  font-family: Arial, Helvetica, sans-serif;
  font-size: 1.2em;
  margin-left: 0;
  margin-bottom: 15px;
}

层叠实战效果

特殊值

有两个特殊值可以赋给任意属性,用于控制层叠: inheritinitial
我们来看看这两个特殊值。

使用 inherit 关键字

有时,我们想用继承代替一个层叠值。这时候可以用inherit关键字。
可以用它来覆盖另一个值,这样该元素就会继承其父元素的值。

假设我们要给网页加上一个浅灰色的页脚。
在页脚上有一些链接,但我们不希望这些链接太显眼,因为页脚不是网页的重点。因此要将页脚的链接变成深灰色(如图所示)。

继承了灰色文本颜色的链接

通常我们会给网页的所有链接加上一个字体颜色(如果不加的话,就会以用户代理样式为准)。
这个颜色也会作用于页脚的“Terms of use”链接。为了让页脚的链接变成灰色,需要覆盖颜色值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
<!doctype html>
<head>
  <style>
body {
  font-family: sans-serif;
}

/* 全局的网页链接颜色 */
a:link {
  color: blue;
  text-decoration: none;
}

a:visited {
  color: purple;
}

a:hover {
  text-decoration: underline;
}

a:active {
  color: red;
}

h1 {
  color: #2f4f4f;
  margin-bottom: 10px;
}

.nav {
  margin-top: 10px;
  list-style: none;
  padding-left: 0;
}

.nav li {
  display: inline-block;
}

.nav a {
  color: white;
  background-color: #13a4a4;
  padding: 5px;
  border-radius: 2px;
  text-decoration: none;
}

.nav .featured {
  background-color: orange;
}

/* 页脚的文本设置为灰色 */
.footer {
  color: #666;
  background-color: #ccc;
  padding: 15px 0;
  text-align: center;
  font-size: 14px;
}

/* 从页脚继承文本颜色 */
.footer a {
  color: inherit;
  text-decoration: underline;
}
  </style>
</head>
<body>
  <header class="page-header">
    <h1 id="page-title" class="title">Wombat Coffee Roasters</h1>
    <nav>
      <ul id="main-nav" class="nav">
        <li><a href="/">Home</a></li>
        <li><a href="/coffees">Coffees</a></li>
        <li><a href="/brewers">Brewers</a></li>
        <li><a href="/specials" class="featured">Specials</a></li>
      </ul>
    </nav>
  </header>
  <footer class="footer">
    &copy; 2016 Wombat Coffee Roasters &mdash;
    <a href="/terms-of-use">Terms of use</a>
  </footer>
</body>

第三个规则集覆盖了蓝色的链接色,让页脚链接的层叠值为inherit。因此,它继承了父元素<footer>的颜色。

这么做的好处是,如果页脚发生任何样式改变的话(比如修改第二个规则集,或者被别的样式覆盖), 页脚链接的颜色就会跟着页脚其他内容一起改变。
比如,当页脚文本变为更深的灰色时,其中的链接也会跟着改变。

还可以使用inherit关键字强制继承一个通常不会被继承的属性,比如边框和内边距。通常在实践中很少这么做

使用 initial 关键字

有时,你需要撤销作用于某个元素的样式。这可以用initial关键字来实现。
每一个CSS属性都有初始(默认)值。如果将initial值赋给某个属性,那么就会有效地将其重置为默认值,这种操作相当于硬复位了该值。
下图展示了给页脚链接赋以 initial 而不是 inherit 时的效果。

默认的颜色值为黑色

1
2
3
4
.footer a {
  color: initial;
  text-decoration: underline;
}

因为在大多数浏览器中,黑色是color属性的初始值,所以color: initial等价于color: black

这么做的好处是不需要思考太多。如果想删除一个元素的边框,设置 border: initial 即可。
如果想让一个元素恢复到默认宽度,设置 `width: initial 即可。

你可能已经习惯了使用auto来实现这种重置效果。实际上,用 width: auto 是一样的,因为 width 的默认值就是 auto。

但是要注意,auto不是所有属性的默认值,对很多属性来说甚至不是合法的值。比如 border-width: autopadding: auto 是非法的,因此不会生效。可以花点时间研究一下这些属性的初始值,不过使用initial更简单。

说明:
声明 display: initial 等价于 display: inline。不管应用于哪种类型的元素,它都不会等于 display: block
这是因为 initial 重置为属性的初始值,而不是元素的初始值。inline 才是 display 属性的初始值。

简写属性

简写属性是用于同时给多个属性赋值的属性。比如font是一个简写属性,可以用于设置多种字体属性。
它指定了 font-style、font-weight、font-size、font-height 以及 font-family。

1
font: italic bold 18px/1.2 "Helvetica", "Arial", sans-serif;

还有如下属性。

  • background 是多个背景属性的简写属性:background-color、background-image、background-size、background-repeat、background-position、background-origin、background-chip 以及 background-attachment。
  • border 是 border-width、border-style 以及 border-color 的简写属性,而这几个属性也都是简写属性。
  • border-width 是上、右、下、左四个边框宽度的简写属性。

简写属性可以让代码简洁明了,但是也隐藏了一些怪异行为。

简写属性会默默覆盖其他样式

大多数简写属性可以省略一些值,只指定我们关注的值。
但是要知道,这样做仍然会设置省略的值,即它们会被隐式地设置为初始值。这会默默覆盖在其他地方定义的样式。
比如,如果给网页标题使用简写属性 font 时,省略 font-weight,那么字体粗细就会被设置为 normal

简写属性会设置省略值为其初始值

1
2
3
4
5
6
7
h1 {
    font-weight: bold;
}

.title {
    font: 32px Helvetica, Arial, sans-serif;
}

简写属性等价的展开属性:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
h1 {
  font-weight: bold;
}

.title {
  font-style: normal;
  font-variant: normal;
  font-weight: normal;
  font-stretch: normal;
  line-height: normal;
  font-size: 32px;
  font-family: Helvetica, Arial, sans-serif;
}

<h1> 添加这些样式会显示成普通的字体,而不是加粗的字体。这些样式也会覆盖从祖先元素继承的字体样式。
在所有的简写属性里,font 的问题最严重,因为它设置的属性值太多了。
因此,要避免在 <body> 元素的通用样式以外使用 font。当然,其他简写属性也可能会遇到一样的问题,因此要当心。

理解简写值的顺序

简写属性会尽量包容指定的属性值的顺序。
可以设置 border: 1px solid black 或者 border: black 1px solid,两者都会生效。
这是因为浏览器知道宽度、颜色、边框样式分别对应什么类型的值。

但是有很多属性的值很模糊。在这种情况下,值的顺序很关键。理解这些简写属性的顺序很重要。

上、右、下、左

当遇到像 marginpadding 这样的属性,还有为元素的四条边分别指定值的边框属性时,开发者容易弄错这些简写属性的顺序。
这些属性的值是按顺时针方向,从上边开始的。

记住顺序能少犯错误。它的记忆口诀是 TRouBLe:top(上)、right(右)、bottom(下)、left(左)

用这个口诀给元素设置四边的内边距。如图所示的链接,上内边距为 10px,右内边距为 15px,下内边距为 0,左内边距为 5px。
虽然这些内边距看起来不是很均匀,但是可以说明简写属性的顺序。

元素的每个方向的内边距都不一样

1
2
3
4
5
6
7
.nav a {
  color: white;
  background-color: #13a4a4;
  padding: 10px 15px 0 5px;
  border-radius: 2px;
  text-decoration: none;
}

这种模式下的属性值还可以缩写。如果声明结束时四个属性值还剩一个没指定,没有指定的一边会取其对边的值。
指定三个值时,左边和右边都会使用第二个值。指定两个值时,上边和下边会使用第一个值。
如果只指定一个值,那么四个方向都会使用这个值。因此下面的声明都是等价的。

1
2
3
padding: 1em 2em;
padding: 1em 2em 1em;
padding: 1em 2em 1em 2em;

下面的声明也是等价的。

1
2
3
4
padding: 1em;
padding: 1em 1em;
padding: 1em 1em 1em;
padding: 1em 1em 1em 1em;

对很多开发人员而言,比较难的是指定三个值时。记住,这种情况指定了上、右、下的值。
因为没有指定左边的值,所以它会取与右边相等的值。第二个值就会作用到左边和右边。
因此 padding: 10px 15px 0 是设置左右内边距为 15px,上内边距为 10px,下内边距为 0。

不过,大多数情况只需要指定两个值。尤其对于较小的元素,左右的内边距最好大于上下内边距。这种样式很适合网页的按钮或者导航链接,如图所示:

很多元素在水平方向的内边距较大会更好看些

1
2
3
4
5
6
7
.nav a {
  color: white;
  background-color: #13a4a4;
  padding: 5px 15px;
  border-radius: 2px;
  text-decoration: none;
}

因为很多属性遵循这个顺序,所以最好记住它

水平、垂直

"TRouBLe" 口诀只适用于分别给盒子设置四个方向的值的属性。
还有一些属性只支持最多指定两个值,这些属性包括 background-positionbox-shadowtext-shadow(虽然严格来讲它们并不是简写属性)。
这些属性值的顺序跟 padding 这种四值属性的顺序刚好相反。
比如,padding: 1em 2em 先指定了垂直方向的上/下属性值,然后才是水平方向的右/左属性值,而 background-position: 25% 75% 则先指定水平方向的右/左属性值,然后才是垂直方向的上/下属性值。

虽然看起来顺序相反的定义违背了直觉,原因却很简单:这两个值代表了一个笛卡儿网格。
笛卡儿网格的测量值一般是按照 x, y(水平,垂直)的顺序来的。比如,如图所示,要给元素加上一个阴影,就要先指定x(水平)值。

盒阴影的位置为10px2px

1
2
3
4
5
/* 阴影向右偏移 10px, 向下偏移 2px */
.nav .featured {
  background-color: orange;
  box-shadow: 10px 2px #6f9090;
}

第一个(较大的)值指定了水平方向的偏移量,第二个(较小的)值指定了垂直方向的偏移量。

如果属性需要指定从一个点出发的两个方向的值,就想想“笛卡儿网格”。如果属性需要指定一个元素四个方向的值,就想想“时钟”。

总结

  • 控制选择器的优先级。
  • 不要混淆层叠和继承。
  • 某些属性会被继承,包括文本、列表、表格边框相关的属性。
  • 不要混淆 initialauto 值。
  • 简写属性要注意 TRouBLe 的顺序,避免踩坑。