28. 继承和层叠
- 详细资料
- 发布于 2012年9月18日
- 点击数:4153
序言
继承和层叠是 CSS 最基本的两个概念。每个使用 CSS 的人都必须理解它们。幸运的是,这两个概念并不难掌握,虽然有些细节有点难以记忆。
这两个概念是紧密相关但又各不相同的。继承关系到 HTML 标记中的元素如何从其父(包含)元素继承属性,并将这些属性传递给它们的子对象,而层叠与应用到某个 HTML 文档的 CSS 声明有关,也与相互冲突的规则会不会互相覆盖有关。
在本章中我们将详细讨论这两个概念。继承可能比较容易掌握一些,因此我将从这一部分开始,然后逐渐进入到错综复杂的层叠概念。点此下载本章范例的源代码;压缩文件中包含有已制作完成的 CSS 和 HTML,以及 HTML 初始模板,让你在学习这些例子的时候可以自己试着操作。
本文内容如下:
继承
CSS 中的继承是指某些属性从父元素传递到其子元素的机制。这与基因遗传是非常相似的。如果父母有着蓝色的眼睛,他们的孩子很可能也会有蓝色的眼睛。
不是所有 CSS 属性都是继承的,因为这对某些属性是没有意义的。比如,边距就不是继承的,因为一个子元素不太可能需要和其父对象具有同样的边距。在大多数情况下,仅靠常识就能知道哪些属性是继承的,哪些不是,但为了真正弄清楚,你还是需要在CSS 2.1 规范属性一览表里面查看每个属性。
为什么说继承很有用
为什么 CSS 会有继承机制呢?想想看如果没有继承会怎样,这样你就能明白这个问题的答案了。你必须逐个指定字体类型、字体大小和文本颜色之类的属性——而且是为每个元素类型单独指定。
利用继承,你就可以指定比如说 html
或 body
元素的字体属性,而所有其它所有元素就都会继承这些属性。你可以对某个特定的容器元素指定背景和前景颜色,并且前景颜色会自动被容器中的所有子元素继承。背景颜色是不能继承的,但 background-color
的初始值是 transparent
,这表示父对象的背景将会透过子对象。这个效果就好像通过继承获得背景颜色一样,但不妨设想一下如果背景图片也被继承的话将会发生什么!每个子对象都将有和其父对象一样的背景图片,结果看起来就会像一张乱七八糟的拼图,因为每个元素的背景都将会“重来一次”。
继承是如何进行的
HTML 文档中的每个元素都将从其父对象那里继承所有的可继承属性,除了根元素(html
),它没有父对象。
继承属性有效与否是取决于其它因素的,这一点将在稍后的层叠部分讲到。就像一个有蓝色眼睛的妈妈也可能会有棕色眼睛的小孩,如果爸爸是棕色眼睛的话,CSS 中的继承属性也可以被覆盖。
一个关于继承的例子
-
在你喜欢的文本编辑器里新建一个文件,将下面的 HTML 文档拷进去,另存为 inherit.html。
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd"> <html lang="en"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>Inheritance</title> </head> <body> <h1>Heading</h1> <p>A paragraph of text.</p> </body> </html>
如果你在 web 浏览器中打开该文件,将会看到一个非常枯燥的页面,跟你的浏览器的默认风格一样。
-
在你的文本编辑器中新建一个的空白文件,将下面的 CSS 规则拷进去,将其另存为 style.css,保存路径跟 HTML 文件一样。
html { font: 75% Verdana,sans-serif; }
-
在
</head>
标签前面插入下面代码,将样式表链接到你的 HTML 文档:<link rel="stylesheet" type="text/css" href="/style.css">
-
保存修改后的 HTML 文件,你的浏览器中对其重新加载。字体将从浏览器的默认值(通常是 Times 或者 Times New Roman) 变成 Verdana。如果你的电脑没有安装 Verdana 字体,则会按照你在浏览器设置中指定的值,以默认的 sans serif 字体来显示文本。
此外,文本大小还会缩小,仅有未经样式处理之前的 3/4。
在这里我们指定的 CSS 规则只会用到
html
元素上。我们没有对标题或段落指定任何的规则,但现在所有的文本都成了 Verdana,大小都变成了默认大小的 75%.这是为什么呢?答案就是继承。font
属性是一种缩写属性,用来设置所有的字体相关属性。在这里我们只写出了两个字体属性——字体大小和字体类型——但这个规则与下面的代码是一回事:html { font-style: normal; font-variant: normal; font-weight: normal; font-size: 75%; line-height: normal; font-family: Verdana,sans-serif; }
所有这些属性都是继承的,因此
body
元素将从html
元素中继承它们,然后将它们传递给其子对象——标题和段落。等一下!在继承字体大小的时候发生了一些可疑的事情,是吧?
html
元素的字体大小设置的是 75%,但是是什么的 75%呢?而且
body
的字体大小本来应该是其父对象字体大小的 75%,而标题和段落的字体大小应该是body
元素的 75%,对吧?所继承的值并不是我们所指定的值——即我们在样式表上写的值——而是一种叫作计算值的东西。对字体大小来说,计算值就是一个以像素为单位的绝对值。对于
html
元素,它没有可以继承的父对象,其字体大小就被设成一个与浏览器默认字体大小有关的百分比值。现在大多数的浏览器都有默认的字体大小设置,其值为 16px 的设置,16 的 75% 是 12,因此html
元素的字体大小计算值可能就是 12px。而这就是被body
继承的那个值,并且会被传递给标题和段落。(标题的字体大小会大一些,因为浏览器使用了一些它自己的内置样式。相关内容将会在下面的层叠部分讲到。)
-
在你的CSS样式表中再加两项声明:
html { font: 75% Verdana,sans-serif; background-color: blue; color: white; }
-
保存 CSS 文件并在浏览器中重新加载该 HTML 文档。
现在背景成了鲜蓝色,所有的文本都成了白色。该规则被应用到了
html
元素上——也就是整个 HTML 文档——这样整个页面背景就成了蓝色。白色的前景颜色是由body
元素继承过来的,并传递给body
的所有子对象——在本例中就是标题和段落。它们没有继承背景,但其背景将默认为transparent
,因此最终视觉效果就是蓝色背景上的白色文本。 -
将另一个新规则添加到样式表中,然后保存并重新加载 HTML 文档:
h1 { font-size: 300%; }
这个规则设置了标题的字体大小。所继承的字体大小用的是百分比值——就是浏览器默认值的 75%,我们假设它等于 12px——因此标题大小将是 12px 的 300%,或者说 36px.
强制继承
你可以强行规定某种继承——甚至是那些默认情况下不能继承的属性——通过 inherit
这个关键字就可以做到。但是你应当谨慎使用该关键字,因为微软的 IE(IE7 及以下版本)并不支持它。
下面的规则将使所有的段落从它们的父对象那里继承所有的背景属性:
p {
background: inherit;
}
通过缩写属性,你就可以使用 inherit
,而不必出所有的属性值。你只能这样:要么对所有对象都使用缩写,要么就不用缩写——你不能用普通写法指定一些值,同时又对其它值使用 inherit
,因为这些值可以以任何顺序出现,因此无法指定哪个是我们想要继承的值。
你并不需要经常用到强制继承。强制继承对“取消”一个冲突规则中的某个声明,或者避免写死某些值是很有用的。比如,看看下面这个典型的导航菜单。
<ul id="nav">
<li><a href="/">Home</a></li>
<li><a href="/news/">News</a></li>
<li><a href="/products/">Products</a></li>
<li><a href="/services/">Services</a></li>
<li><a href="/about/">About Us</a></li>
</ul>
如果想用一个水平菜单来显示这个链接列表的话,你可以使用下面的 CSS:
#nav {
background: blue;
color: white;
margin: 0;
padding: 0;
}
#nav li {
display: inline;
margin: 0;
padding: 0 0.5em;
border-right: 1px solid;
}
#nav li a {
color: inherit;
text-decoration: none;
}
本例中,我们在 #nav
规则中将整个列表的背景颜色设置成蓝色。这段 CSS 代码也将前景颜色设置为白色,而且每个列表项和每个链接都继承了这种设置。在列表项规则中我们设置了右边框,但没有指定边框颜色,这意味着它将会使用继承来的前景颜色(白)。对于链接,我们已经使用了 color:inherit
来强制继承和覆盖浏览器的默认链接颜色。
当然我也可以将边框颜色和链接文本颜色设置为白色,但让继承来完成这个工作的好处,在于如果你决定要使用另一个色系,你只需要在一个地方更改颜色就可以了。
层叠
CSS 的意思就是层叠样式表,因此如果告诉你层叠是一个很重要的概念,你应该不会感到惊讶。层叠是一种机制,用于在多个互相冲突的 CSS 声明作用于同一个元素时控制最终结果。下面是三个用于控制CSS声明的应用顺序的主要概念:
- 重要性
- 特异性
- 源顺序
下面我们来逐个地看看这些概念。
重要性是最……呃……重要的。如果两个声明有着相同的重要性,规则的特异性就会决定该应用哪一个声明。如果两个规则有着相同的特异性,最终结果就会由源顺序决定。
重要性
一个 CSS 声明的重要性取决于它是在什么地方被指定的。互相冲突的声明会按照下面的顺序被应用;后面的声明会覆盖先前的声明:
- 用户代理样式表
- 作者样式表中的一般声明
- 用户样式表中的一般声明
- 作者样式表中的重要声明
- 用户样式表中的重要声明
用户代理样式表是浏览器的内置样式表。每个浏览器都有自己的默认规则,用于在用户或网页设计者没有指定样式的情况下显示各种 HTML 元素。比如,未访问链接通常都是蓝色并带有下划线的。
用户样式表是由用户定义的样式表。并非所有浏览器都支持用户样式表,但它们是非常有用的,尤其是对有某种残疾的用户。比如一个有阅读障碍的人可以用一个用户样式表来指定某些字体和颜色。
在 Opera 中,你可以通过Tools(在 Mac 计算机上则是 Opera 菜单) >Preferences…>Advancedtab >Content,点击Style Options…按钮,然后在对话框中的Display里的My style sheet文本框中输入你的用户样式表。如果你希望用户样式表覆盖作者样式表,你也可以在Presentation标签中进行指定,甚至可以在用户界面中添加一个按钮来让你在用户和作者样式表之间进行切换。为了达到这个目的,在Preferences…菜单中全部选择 OK,然后在 Opera 浏览器界面的某处单击右键或者按住 Ctrl键点击,选择Customize…>Buttons标签 >Browser试图,然后将Author Mode按钮拖到你工具栏中的某个地方。
作者样式表就是我们通常说的“样式表”。这是 HTML 文档的作者(或更确切地说是网站设计者)编写的和链接到的(或包含的)样式表。
一般声明就是一般写法的声明。与一般声明相对的就是重要声明,重要声明的后面会写着一个 !important
指令。
你可以发现,在用户样式表中的重要声明会其它任何声明的优先级都要高,这是合理的。比如说,如果阅读障碍患者想让所有的文本都显示为 Comic Sans MS 字体,因为他觉得这样更易于阅读的话,他可能会使用包含以下规则的用户样式表:
* {
font-family: "Comic Sans MS" !important;
}
在这种情况下,不管设计者指定了什么,也不管浏览器的默认字体类型设置如何,一切都会以 Comic Sans MS 显示出来。
浏览器的默认渲染只能在其声明没有被任何用户样式表或作者样式表覆盖的时候才能使用,因为用户代理样式表的优先级是最低的。
坦率的讲,大多数设计者不必对重要性考虑得太多,因为我们对其无能为力。如果某个用户定义了一份可以覆盖我们的 CSS 的用户样式表,我们也无从知道。无论如何,如果他们要这样做,肯定是自有道理的。当然,知道什么是重要性以及它会如何影响我们的 HTML 文档的外观还是很有好处的。
特异性
特异性是每个 CSS 设计者必须理解和琢磨的。它可以被看成一个计量单位,用来衡量一个规则选择符的具体性如何。低特异性的选择符可以跟许多元素匹配(像*
可以匹配文件中所有的元素),而一个高特异性的选择符可能只能匹配一个页面中的某个特定元素(象 #nav
只能匹配带有 nav
的 id
的元素)。
选择符的特异性很容易计算,我们很快就会看到这一点。如果一个既定元素有两个或更多的声明彼此冲突,而所有声明都有着同样的重要性,那么规则中最具体的那个选择符就会“胜出”。
特异性有四个组成部分,我们称之为 a、b、c和 d。“a” 组件的权重最高,“d” 则最低。
“a” 组件是非常简单的,对于一个在 style
属性中的声明来说,它的值是 1,否则就是 0。
“b” 组件的值等于选择符(以 a #
开始的)中的 id
选择符的数量。
“c” 组件的值等于属性选择符——包括类选择符——和伪类的数量。
“d” 组件的值等于选择符中元素类型和伪元素的数量。
在经过数算之后,我们就可以将这四个组成部分结合起来,从而得到任何规则的特异性。在 style
属性中的 CSS 声明没有选择符,因此它们的特异性值就是 1,0,0,0。
我们来看几个例子——看完之后你就会明白特异性值是如何计算的了。
选择符 | a | b | c | d | 特异性值 |
---|---|---|---|---|---|
h1 |
0 | 0 | 0 | 1 | 0,0,0,1 |
.foo |
0 | 0 | 1 | 0 | 0,0,1,0 |
#bar |
0 | 1 | 0 | 0 | 0,1,0,0 |
html>head+body ul#nav *.home a :link |
0 | 1 | 2 | 5 | 0,1,2,5 |
让我们更详细地来看看最后一个例子。在这里 a=0,因为它是一个选择符,而不是 style
属性中的声明。ID 选择符有一个(#nav
),因此 b=1。还有一个属性选择符(.home
)和一个伪类(:link
) ,因此 c=2。还有 5个元素类型(html
,head
,body
,ul
和 a
),因此 d=5。因此最终的特异性值就是 0,1,2,5。
值得注意的是连结符(像 >
,+
以及空格)不会影响一个选择符的特异性。通配选择符(*
)也不会加到特异性值里去。
另外要注意的是引用某个 id
属性的时候,id
选择符与属性选择符的特异性值有着巨大的差异。虽然它们匹配的是同一个元素,它们的特异性值的差异非常大。#nav
的特异性值是0,1,0,0,但 [id="nav"]
的特异性值仅仅是 0、0、1、0。
让我们来看看该算法的实际应用。
-
我们先在 HTML 文档中添加一个段落。
<body> <h1>Heading</h1> <p>A paragraph of text.</p> <p>A second paragraph of text.</p> </body>
-
在你的样式表中添加一个规则,给段落中的文本设置颜色:
p { color: cyan; }
-
保存这两个文件,并在浏览器中重新加载该文档,现在应该出现了两段青色的文本。
-
给第一段设置一个
id
,这样你可以比较容易地用 CSS 选择符选择它。<body> <h1>Heading</h1> <p id="special">A paragraph of text.</p> <p>A second paragraph of text.</p> </body>
-
在你的样式表中对这个特殊段落添加一个规则:
#special { background-color: red; color: yellow; }
-
保存这两个文件,并在浏览器中重新加载该文档,就可以看到艳丽多彩的最终效果。
让我们来看看应用到这两个段落的声明。
你添加的第一个规则对所有段落设置了 color:cyan
。第二个规则设置了一个红色背景,并对 id
为 special
的特定元素设置了 color:yellow
。你的第一个段落同时与两个规则相匹配;因为它是一个段落而且 id
为 special
。
红色背景并不是问题所在,因为它仅仅是为 #special
设定的。但两个规则都包含有 color
属性的声明,这就意味着会有冲突。这两个规则重要性相同——它们都是作者样式表单中的一般声明——因此你得看看这两个选择符的特异性。
第一个规则的选择符是 p
,由于它包含了一个特定的元素类型,因此根据上面的算法它的特异性值为 0,0,0,1.第二个规则的选择符是 #special
,由于它包含了一个 id
选择符,它的特异性值就是 0,1,0,0。0,1,0,0 比 0,0,0,1 更具体,因此 color:yellow
声明胜出,你会得到红色背景黄色文本的最终效果。
源顺序
如果两个声明作用于同一个元素,并有着同样的重要性和同样的特异性,那么最终的辨识标记就是源顺序。在样式表后面的声明将“胜过”前面的声明。
如果你有一个单独的外部样式表,如果存在冲突的话,文件末尾的声明将覆盖前面的那些声明。互相冲突的声明也可以来自于不同的样式表。这种情况下,HTML 文档中样式表的链接,植入或导入顺序将决定哪个声明会得到应用,因此如果在 HTML 文档的 head
部分中链接了两个样式表,后一个链接的样式表会覆盖前一个链接的样式表。让我们在实际例子中来看看这个法则稍后如何运作的:
-
在你的样式表文件的最末尾处添加一条新规则,如下所示:
p { background-color: yellow; color: black; }
-
保存并重新加载网页。现在你有两个匹配所有段落的规则。它们有同样的重要性和同样的特异性(因为选择符是一样的),因此源顺序将会是用来消除歧义的最终机制。
最后的规则指定了
color:black
,这个设置将覆盖先前的规则所指定的color:cyan
。
注意第一个段落并没有受到这个新规则的影响。尽管这个新规则出现在最后,但它的选择符的特异性值比 #special
要低。这清楚地说明了特异性值的优先级比源顺序要高。
总结
继承是层叠是每个 web 设计师必需理解的基础概念。
通过继承,我们可以声明高层元素的属性,并让这些属性遗传给所有派生元素。在默认情况下只有一部分属性才能继承,但可以通过 inherit
关键字来强制继承那些默认不能继承的属性。
在多个声明作用于同一个元素时,层叠解决了所有的冲突问题。重要声明会覆盖重要性较低的声明。在重要性相同的声明之间,规则的特异性值决定了哪个声明会得到应用。此外,若其它所有条件都相同,最终结果将取决于源顺序。