CSS解析:层叠、优先级和继承

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;
}

第一个(较大的)值指定了水平方向的偏移量,第二个(较小的)值指定了垂直方向的偏移量。如果属性需要指定从一个点出发的两个方向的值,就想想“笛卡儿网格”​。如果属性需要指定一个元素四个方向的值,就想想“时钟”​。

  • 8
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值