CSS虽说不是编程语言,但是日常使用中经常有很多误解,发现样式不奏效的情况,所以需要加强下CSS基础。
CSS本质上就是声明规则,即在各种条件下,我们希望产生特定的效果。
如果某个元素有这个类,则应用这些样式。
如果X元素是Y元素的子节点,则应用那些样式。
浏览器会根据这些规则,判断每个规则应该用在哪里,并使用它们去渲染页面。
一、层叠
我们需要理解浏览器如何解析样式规则。每条规则单独来看很简单,但是当两条规则提供了冲突的样式时会发生什么呢?
如果你发现有一条规则没有按照预期生效,可能是因为另一条规则跟它冲突了。要想预测规则最终的效果,就需要理解CSS里的层叠。
CSS的意思时层叠样式表,其中C代表(cascade,层叠)。
例如以下代码,三个规则集尝试给标题设置不同的颜色,哪一个会生效呢?浏览器为了解决这个问题会遵循一系列规则,因此最终的效果可以预测。
<h1 id="page-title" class="title">主要标题</h1>
<style>
h1{
color:red;
}
#page-title {
color:green;
}
.title {
color:blue;
}
</style>
层叠指的就是这一系列规则。它决定了如何解决冲突,是CSS语言的基础。虽然有经验的开发人员对层叠有大体的了解,但是层叠里有些规则还是容易让人误解。
当声明冲突时,层叠会依据三种条件解决冲突。
(1) 样式表的来源:样式是从哪里来的,包括你的样式和浏览器默认样式等。
(2) 选择器优先级:哪些选择器比另一些选择器更重要。
(3) 源码顺序:样式在样式表里的声明顺序。
(一)术语解释
包含在大括号内的一组声明被称作一个声明块。
声明块前面有一个选择器。
body {
color: black;
font-family: Helvetica;
}
选择器和声明块一起组成了规则集(ruleset),一个规则集也简称一个规则。
@规则(at-rules)是指用“@”符号开头的语法。比如@import规则或者@media查询。
(二)样式表的来源
添加到网页里的样式表并不是浏览器唯一使用的样式表,还有其他类型或来源的样式表。你的样式表属于作者样式表,除此之外还有用户代理样式表,即浏览器默认样式。用户代理样式表优先级低,你的样式会覆盖它们。
1.用户代理样式表
浏览器应用了用户代理样式后才会应用你的样式表,即作者样式表。你指定的声明会覆盖用户代理样式表里的样式。如果你在HTML里面链接了多个样式表,那么它们的来源都相同,即作者。
用户代理样式表因为设置了用户普遍需要的样式,所以不会做一些完全超出预期的事情。当你不喜欢默认样式时,可以在自己的样式表里设置别的值。
2.! important声明
样式来源规则有一个例外:标记为重要(important)的声明。如下所示,在声明的后面、分号的前面加上!important,该声明就会被标记为重要的声明。
color:red!important;
标记了!important的声明会被当作更高优先级的来源,因此总体的优先级按照由高到低排列如下所示:
(1) 作者的!important
(2) 作者
(3) 用户代理
3.优先级
如果无法用来源解决冲突声明,浏览器会尝试检查它们的优先级。
浏览器将优先级分为两部分:HTML的行内样式和选择器的样式。
(1)行内样式
如果用HTML的style属性写样式,这个声明只会作用于当前元素。实际上行内元素属于“带作用域的”声明,它会覆盖任何来自样式表或者
<a href="/abc" class="title" style="color:red">红色链接</a>
<style>
.title {
color:blue;
}
</style>
若想在样式表里覆盖行内声明,需要为声明添加!important
,这样能将它提升到一个更高优先级的来源。但如果行内样式也被标记为!important
,就无法覆盖它了。最好是只在样式表内用!important
。
(2)选择器优先级
优先级的第二部分由选择器决定。比如,有两个类名的选择器比只有一个类名的选择器优先级更高。
不同类型的选择器有不同的优先级。比如,ID选择器比类选择器优先级更高。实际上,ID选择器的优先级比拥有任意多个类的选择器都高。同理,类选择器的优先级比标签选择器(也称类型选择器)更高。
优先级的准确规则如下:
❑ 如果选择器的ID数量更多,则它会胜出(即它更明确)。
❑ 如果ID数量一致,那么拥有最多类的选择器胜出。
❑ 如果以上两次比较都一致,那么拥有最多标签名的选择器胜出。
<html>
<body>
<header class="page-header"><h1 id="page-title" class="title" >主要标题</h1></header>
</body>
<style>
/* 4个标签 */
html body header h1{
color:blue;
}
/* 3个标签和1个类 */
html header.page-header h1{
color:orange;
}
/* 2个类 */
.page-header .title {
color:green;
}
/* 1个ID */
#page-title {
color:red;
}
</style>
</html>
最明确的选择器是有1个ID的,因此标题的颜色最终为红色。
第二明确的是有2个类的。如果没有出现带ID选择器的,则2个类的声明会生效。
最后,4个标签名的选择器最不明确,它有4个元素类型(即标签名),但是没有ID或者类。
(3)优先级标记
一个常用的表示优先级的方式是用数值形式来标记,通常用逗号隔开每个数。比如,“1,2,2”表示选择器由1个ID、2个类、2个标签组成。优先级最高的ID列为第一位,紧接着是类,最后是标签。
现在通过比较数值就能决定哪个选择器优先级更高(更明确)。“1,0,0”的优先级高于“0,2,2”甚至“0,10,0”(尽管不推荐写一个长达10个类名的选择器),因为第一个数(ID)有最高优先级。
(4)关于优先级冲突的解决
例如下面代码,.feature
的类选择器添加橘黄色背景,但是没有成功。#main-nav a
选择器包含了一个ID,覆盖了类选择器。
<html>
<body>
<ul id="main-nav" class="nav">
<li><a>北京</a></li>
<li><a>上海</a></li>
<li><a>广州</a></li>
<li><a class="feature">深圳</a></li>
</ul>
</body>
<style>
#main-nav {
margin-top:10px;
list-style:none;
}
#main-nav li {
display:inline-block;
}
#main-nav a{
color:white;
background-color:green;
padding:5px;
border-radius:2px;
text-decoration:none;
}
.feature {
background-color:orange;
}
</style>
</html>
最快的方法是将!important
添加到想要设置的元素的声明上。
.feature {
background-color:orange !important;
}
这个方法之所以生效,是因为!important
注释将声明提升到了更高优先级的来源。这个方法的确简单,但也很低级。它可能解决了眼前的问题,但是会在以后带来更多问题。一旦给很多声明加上!important
,要覆盖已设置为important的声明时,该怎么做呢?
当给一些声明加上!important
时,就会先比较来源,再使用常规的优先级规则。最终会让一切回到起点:一旦引入一个!important
,就会带来更多的!important
。
那么更好的方法是什么?请不要试图绕开选择器优先级,而是利用它来解决问题。何不提升选择器的优先级呢?
#main-nav .feature {
background-color:orange !important;
}
这个方法也奏效了。现在你的选择器有1个ID和1个类,优先级为“1,1,0”,比#main-nav a
(优先级为“1,0,1”)高。因此,橘黄色背景是能够应用到元素上的。
但是这个方法还能改进。不提升第二个选择器的优先级,而是降低第一个选择器的优先级。导航链接元素同时还有一个类:<ul id="main-nav" class="nav">
。所以可以修改CSS,通过类名而不是ID来选中元素。
.nav a{
color:white;
background-color:green;
padding:5px;
border-radius:2px;
text-decoration:none;
}
通过这些例子可以发现,优先级容易发展为一种“军备竞赛”。在大型项目中这一点尤为突出。通常最好让优先级尽可能低,这样当需要覆盖一些样式时,才能有选择空间。
(5)源码顺序
层叠的第三步,也是最后一步,是源码顺序。如果两个声明的来源和优先级相同,其中一个声明在样式表中出现较晚,或者位于页面较晚引入的样式表中,则该声明胜出。
也就是说,可以通过控制源码顺序,来给特殊链接添加样式。如果两个冲突选择器的优先级相同,则出现得较晚的那个胜出。
.nav a{
color:white;
background-color:green;
padding:5px;
border-radius:2px;
text-decoration:none;
}
a.feature {
background-color:orange ;
}
在这个方法里,选择器优先级相同。源码顺序决定了哪个声明作用于特殊链接,最终产生了橘黄色的特殊按钮。
链接样式与源码顺序
当给链接加样式要按照一定的顺序书写选择器。这是因为源码顺序影响了层叠。
a:link{
color:blue;
text-decoration:none;
}
a:visited{
color:purple;
}
a:hover{
text-decoration:underline;
}
a:active{
color:red;
}
书写顺序之所以很重要,是因为层叠。优先级相同时,后出现的样式会覆盖先出现的样式。如果一个元素同时处于两个或者更多状态,最后一个状态就能覆盖其他状态。
如果用户将鼠标悬停在一个访问过的链接上,悬停效果会生效。如果用户在鼠标悬停时激活了链接(即点击了它),激活的样式会生效。
层叠值
浏览器遵循三个步骤,即来源、优先级、源码顺序,来解析网页上每个元素的每个属性。
如果一个声明在层叠中“胜出”,它就被称作一个层叠值。
元素的每个属性最多只有一个层叠值。网页上一个特定的段落(
)可以有一个上外边距和一个下外边距,但是不能有两个不同的上外边距或两个不同的下外边距。
如果CSS为同一个属性指定了不同的值,层叠最终会选择一个值来渲染元素,这就是层叠值。
层叠值——作为层叠结果,应用到一个元素上的特定属性的值。
如果一个元素上始终没有指定一个属性,这个属性就没有层叠值。还是拿段落举例,可能就没有指定的边框或者内边距。
(6)经验总结
处理层叠时有两条通用的经验法则。
-
在选择器中不要使用ID。就算只用一个ID,也会大幅提升优先级。当需要覆盖这个选择器时,通常找不到另一个有意义的ID,于是就会复制原来的选择器,然后加上另一个类,让它区别于想要覆盖的选择器。
-
不要使用!important。它比ID更难覆盖,一旦用了它,想要覆盖原先的声明,就需要再加上一个!important,而且依然要处理优先级的问题。
这两条规则是很好的建议,但不必固守它们,因为也有例外。不要为了赢得优先级竞赛而习惯性地使用这两个方法。
二、继承
还有最后一种给元素添加样式的方式:继承。
经常有人会把层叠跟继承混淆。虽然两者相关,但是应该分别理解它们。
如果一个元素的某个属性没有层叠值,则可能会继承某个祖先元素的值。比如通常会给元素加上font-family,里面的所有祖先元素都会继承这个字体,就不必给页面的每个元素明确指定字体了。如图展示了继承是如何顺着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也能被继承。
我们可以在适当的场景使用继承。比如给body元素应用字体,让后代元素继承该字体。
body {
font-family:sans-serif;
}
将属性加到body上会在整个网页上生效。而将属性加到特定元素上,则只会被它的后代元素继承。继承属性会顺序传递给后代元素,直到它被层叠值覆盖。
(一)开发者工具
使用开发者工具能够看到哪些元素应用了哪些样式规则,以及为什么应用这些规则。层叠和继承都是抽象的概念,使用开发者工具是最好的追踪方式。在一个页面元素上点击鼠标右键,选择弹出菜单上的检查元素,就能打开开发者工具。
有很多细节可以帮助开发人员弄清楚一个元素的样式是怎么产生的。
靠近顶部的样式会覆盖下面的样式。被覆盖的样式上划了删除线。
右侧显示了每个规则集的样式表和行号,你可以在源代码中找到它们。这样就能准确判断哪个元素继承了哪些样式以及这些样式的来源。
还可以在顶部的筛选框中选择特定的声明,同时隐藏其他声明。
(二)特殊值
有两个特殊值可以赋给任意属性,用于控制层叠:inherit和initial。我们来看看这两个特殊值。
1.使用inherit关键字
有时,我们想用继承代替一个层叠值。这时候可以用inherit
关键字。可以用它来覆盖另一个值,这样该元素就会继承其父元素的值。
假设我们要给网页加上一个浅灰色的页脚。在页脚上有一些链接,但我们不希望这些链接太显眼,因为页脚不是网页的重点。因此要将页脚的链接变成深灰色。
a:link {
color:blue;
}
.footer {
color:#666;
background-color:#ccc;
text-align:center;
padding:15px 0;
font-size:14px;
}
.footer a {
color:inherit;
text-decoration:underline;
}
第三个规则集覆盖了蓝色的链接色,让页脚链接的层叠值为inherit。因此,它继承了父元素<footer>
的颜色。
这么做的好处是,如果页脚发生任何样式改变的话(比如修改第二个规则集,或者被别的样式覆盖),页脚链接的颜色就会跟着页脚其他内容一起改变。比如,当页脚文本变为更深的灰色时,其中的链接也会跟着改变。
2.使用initial关键字
有时,你需要撤销作用于某个元素的样式。这可以用initial关键字来实现。每一个CSS属性都有初始(默认)值。如果将initial值赋给某个属性,那么就会有效地将其重置为默认值,这种操作相当于硬复位了该值。
.footer a {
color:initial;
text-decoration:underline;
}
如图,链接变成黑色字体。因为在大多数浏览器中,黑色是color属性的初始值,所以color: initial等价于color: black。
这么做的好处是不需要思考太多。如果想删除一个元素的边框,设置border: initial即可。如果想让一个元素恢复到默认宽度,设置width: initial即可。
(三)简写属性
简写属性是用于同时给多个属性赋值的属性。
比如font是一个简写属性,可以用于设置多种字体属性。它指定了font-style、font-weight、font-size、font-height以及font-family。
p {
font: italic bold 15px / 1.5 Arial, sans-serif;
}
简写属性可以让代码简洁明了,但是也隐藏了一些怪异行为。
1.简写属性会默默覆盖其他样式
大多数简写属性可以省略一些值,只指定我们关注的值。但是要知道,这样做仍然会设置省略的值,即它们会被隐式地设置为初始值。这会默默覆盖在其他地方定义的样式。
比如,如大多数简写属性可以省略一些值,只指定我们关注的值。但是要知道,这样做仍然会设置省略的值,即它们会被隐式地设置为初始值。这会默默覆盖在其他地方定义的样式。
<h1 class="title">中华人民共和国</h1>
<style>
h1 {
font-weight:bold;
}
.title {
font: italic 32px Arial, sans-serif;
}
</style>
乍一看可能会觉得<h1 class="title">
会将标题加粗,但结果不是。
给<h1>
添加这些样式会显示成普通的字体,而不是加粗的字体。这些样式也会覆盖从祖先元素继承的字体样式。在所有的简写属性里,font的问题最严重,因为它设置的属性值太多了。
.title {
font-style: normal; /* 默认值 */
font-variant: normal; /* 默认值 */
font-weight: normal; /* 默认值 */
font-size: 16px; /* 可以根据需要调整 */
line-height: 1.5; /* 可以根据需要调整 */
font-family: 'Roboto', sans-serif; /* 首选字体和备选字体 */
}
因此,要避免在<body>
元素的通用样式以外使用font。当然,其他简写属性也可能会遇到一样的问题,因此要当心。
2.理解简写值的顺序
简写属性会尽量包容指定的属性值的顺序。可以设置border: 1px solid black
或者border:black 1px solid
,两者都会生效。这是因为浏览器知道宽度、颜色、边框样式分别对应什么类型的值。但是有很多属性的值很模糊。
在这种情况下,值的顺序很关键。理解这些简写属性的顺序很重要。
上、右、下、左
当遇到像margin、padding这样的属性,还有为元素的四条边分别指定值的边框属性时,开发者容易弄错这些简写属性的顺序。这些属性的值是按顺时针方向,从上边开始的。记住顺序能少犯错误。它的记忆口诀是TRouBLe:top(上)、right(右)、bottom(下)、left(左)。
a {
padding: 10px 15px 0 5px;
}
这种模式下的属性值还可以缩写。如果声明结束时四个属性值还剩一个没指定,没有指定的一边会取其对边的值。指定三个值时,左边和右边都会使用第二个值。指定两个值时,上边和下边会使用第一个值。如果只指定一个值,那么四个方向都会使用这个值。
水平、垂直
“TRouBLe”口诀只适用于分别给盒子设置四个方向的值的属性。还有一些属性只支持最多指定两个值,这些属性包括background-position、box-shadow、text-shadow(虽然严格来讲它们并不是简写属性)。
这些属性值的顺序跟padding这种四值属性的顺序刚好相反。比如,padding: 1em 2em先指定了垂直方向的上/下属性值,然后才是水平方向的右/左属性值,而background-position: 25% 75%则先指定水平方向的右/左属性值,然后才是垂直方向的上/下属性值。
虽然看起来顺序相反的定义违背了直觉,原因却很简单:这两个值代表了一个笛卡儿网格。笛卡儿网格的测量值一般是按照x, y(水平,垂直)的顺序来的。比如,如图所示,要给元素加上一个阴影,就要先指定x(水平)值。
/* 阴影向右偏移10px 向下偏移2px */
.nav {
background-color:orange;
box-shadow:10px 2px #6f9090;
}
第一个(较大的)值指定了水平方向的偏移量,第二个(较小的)值指定了垂直方向的偏移量。如果属性需要指定从一个点出发的两个方向的值,就想想“笛卡儿网格”。如果属性需要指定一个元素四个方向的值,就想想“时钟”。