正如我们平时口中所说的 CSS 中的优先级是用来确定当多个 CSS 规则应用于同一个元素时,哪一个规则将被应用的算法。我们通过老师或者别的同事口中得知 CSS 优先级由四个不同的级别组成,分别是:内联样式、ID 选择器、类选择器和标签选择器 。优先级的级别由高到低排列如下:
- 内联样式 : 散布在 HTML 标签内使用 style 属性的样式
- ID 选择器 : 通过
#id
语法选择的元素 - 类、属性和伪类选择器 :通过
.class
、[type="text"]
或:hover
等选择的元素 - 标签选择器和伪元素选择器 : 通过标签名(如 div、p )或 ::before 和** ::after** 等伪元素选择的元素
在应用 CSS 样式时,对于相同的样式属性,使用具有最高优先级的规则进行应用。如果两个规则具有相同的优先级,那么后面定义的规则优先级更高。
例如,以下规则将会被按照优先级从高到低的顺序应用:
css
#header {
background-color: red;
color: white;
}
.header {
background-color: blue;
}
div {
background-color: yellow;
}
在这个例子中,ID 选择器的样式具有最高优先级,其次是类选择器的样式,标签选择器的样式具有最低优先级。因此,如果存在 id 为 header
的元素,它将使用红色背景和白色文字。如果不存在 ID 为 header
的元素,但是存在 class="header"
的元素,则它将应用蓝色背景。如果没有 ID 选择器或类选择器匹配,则使用标签选择器定义的黄色背景。
对于 如果两个规则具有相同的优先级,那么后面定义的规则优先级更高
这句话该怎么理解?
看个代码示例:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
.class2{
color: red;
}
.class1{
color: blue;
}
</style>
</head>
<body>
<div class="class1 class2">
Hello, CSS
</div>
</body>
</html>
上面我们定义了两个 class
分别是 class2
和 class1
,这两个规则具有相同的优先级。
在元素使用的时候, class2
在 class1
后面,但最后的文字颜色是 blue
,这说明样式优先级与我们在元素的 class
属性使用顺序无关,只与其定义顺序有关,相同属性后面的会覆盖前面的,因为 class1
定义在 class2
后面,所以其优先级要更高。
如果单纯的理解上面的规则,确实以上的规则已经满足了我们大部分的开发需求,也没有任何问题。直到我遇到了两个关键词: !important 和 @layer。这两个关键词的出现使得上面的规则已经不能成立,因为我们可以通过这两个词来重新定义其样式的优先级。
具体这两个关键词是如何影响优先级的,我们继续往下阅读。
本小节节选 自掘金小册《CSS 工程化核心原理与实战》,如有需要下单记得使用五折优惠码:oI3M36I4
CSS 的起源
理解 CSS 的起源是理解下面知识点关键点,CSS 中有三个起源:
- 创作人员(开发者)
- 用户(使用网页的人员)
- 用户代理(浏览器)
从以上字面意思我们并不难理解这三个起源的概念,默认情况下这三者的优先级是从上到下,即开发者设置的样式优先级最高,这很好理解,如果我们开发者的样式级别低,那我们写的样式会被后两者给覆盖,我们写的样式没有任何意义。
对于我们没设置值的属性,浏览器默认有自己的规则,不同的浏览器可能规则不尽相同。
!important 真的是无所不能吗?
下面我们来想一个问题:
css
<input type="hidden">
对于上面的元素为什么默认是不可见的,我们能不能通过设置 display: block
使其可见?
在回答这个问题之前,我们先来看一下它在浏览器具体的样式是怎么样的。
虽然我们没设置任何样式给该元素,但从浏览器的控制台可以看出,它有来自user agent stylesheet
(浏览器)默认的样式。
而且对于其可见性是:display: none !important;
说到这可能会有同学立马想到,我们自己再定义一个样式覆盖浏览器的默认样式不就可以了?
我们尝试一下,而且是设置级别很高的 style
属性的方式;
可以看出虽然在控制台中我们的样式 "成功" 的覆盖(浏览器设置了中划线,可能是一个bug)了来自 user agent stylesheet
的样式,但页面上依然并没有任何元素显出来。
这说明我们虽然用了权重很高的方式去覆盖默认的样式,但依然没有让元素显示出来。
产生这样的效果的原因是由于 !important
的缘故。
虽然我们在平时的开发中想要覆盖某个样式的时候一言不可就用 !important
的方式,但在这里却不灵了。
原因就在于: !important
会颠倒上面我们说的三大起源的优先级顺序。
即:没有使用 !important
的情况:
创作人员(开发者)> 用户(使用网页的人员)> 用户代理(浏览器)
但:使用了 !important
的情况:
用户代理(浏览器)> 用户(使用网页的人员)> 创作人员(开发者)
这就是为什么我们无法覆盖其默认的样式原因,就是浏览器使用了 !important
,产生的优先级最高。!important
虽然好用,但是在平时开发中要慎用,因为除了会破坏优先级规则之外可能还会带来意想不到的影响。
我们先来看一张来自 css-tricks网站一篇文章 中的一张图
刚才我们所讲的正好对应图中最顶部黄色的部分(origin & importance),从图上也可以印证了我们所述。
通过上面的图我们还能发现几个重要的点:
- 通过设置元素的
style
属性样式就是会比我们写的selector
写的样式优先级要高 - 印证了本文最开始我们所说的
selector
中的优先级,即 :ID 选择器 > 类、属性和伪类选择器 > 标签选择器和伪元素选择器 - CSS 的级联关系是很多的,我们平时写的样式优先级,基本都是在
Element Attached
和Selector Specificity
这两层级。对于Context
和Order of Appearance
这两级我们先不做介绍。
结合我们所学,上面的图片我们总结简化如下:
其实文章到这里就已经基本上涵盖了我们日常开发中绝大数的场景,但事实上并没有那么简单,正如上面我们说到了 !important
的同时还提及到了另一个关键词 @layer
。
@layer
在具体讲解 layer 之前,我们先来说一下 @ 是干什么的。
在 CSS 中,存在一种特殊的规则称为 @ 规则(at-rules ),它们以 @ 符号开头,用于声明一些特殊的指令或规则,当浏览器在解析 CSS 的时候,如果遇到了 @xxx
这样的规则,会进行特殊的处理。这些规则通常在 CSS 样式表中最顶层进行声明,以区别于普通的样式规则,作用范围可能涵盖整个文档或特定的元素组。
在 CSS 中有两种规则,一种是我们写的普通的样式规则,如:font-size: 14px;
还有一种是以 @ 开头的,后面跟着特定的关键字的,如:@charset "UTF-8";
在开发中我们可能遇到过一些这样的规则集,如:
- @charset:用于声明 CSS 文件的字符编码。
- @import:用于导入外部的 CSS 文件,将另一个 CSS 文档中的样式引入到当前文档中。
- @media:用于定义媒体查询规则,即根据不同的媒介类型、视口尺寸或设备特性等,对不同的设备或场景应用不同的样式。
- @font-face:用于定义自定义字体,可以将自定义的字体文件加载到页面,从而在页面中使用该字体。
- @keyframes:用于定义关键帧动画,即定义动画过程的关键帧和对应的样式。
当然还有更多的 @ 规则,这里我们不在一一列举,感兴趣的同学可以上网进行查阅具体的用法。
当我们明白了 @ 之后,我们再来看 @layer,其本质也是一种特殊的规则集。
@layer 出现在 CSS3 中。根据官方文档的解释, @layer 规则的作用是将样式层分组。
我们来考虑一个场景:
在页面上我们设置了某个 div
字体的大小是:font-size: 16px
html
<div class="lg-text">
</div>
css
.lg-text{
font-size: 16px;
}
但如果我们引入其它的样式库文件,恰好这个第三方的样式文件中也有 .lg-text
选择器,如:
css
// 引入的第三方库中也有同名的选择器,但font-size是14px
.lg-text{
font-size: 14px;
}
这个时候如果稍不留神,我们写的样式就有可能被覆盖。完整代码如下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
.lg-text{
font-size: 16px;
}
</style>
<link rel="stylesheet" href="./third.css" type="text/css"/>
</head>
<body>
<div class="lg-text">
Hello, CSS
</div>
</body>
</html>
运行效果如下,不出意外我们的样式被第三方样式库的样式给覆盖了......
解决这个问题有几种办法:
- 修改第三方样式库的代码,但这方式会破坏别人的代码可能会造成别的元素样式错乱,这种方式不可取
- 提高我们的样式的优先级,比如,把我们写的代码放在最后加载。这种方式是比较常用的方式,可以使用。但这种方式如果在某些不允许更改顺序的情况下就不能再生效。
- 直接在我们的代码中使用
!important
,可以解决问题,但彻底把样式固定了,后面如果我们想再改样式,只能再使用!important
,一直这样循环下去......
诸如此类的问题还有很多很多,只不过平时我们只注重解决问题本身就好,从不管方式会带来怎么样的后果,这样造成的代码只会越来越冗长而且越来越难维护,最后只得重构......
下面我们通过 @layer 的方式来看一下这个问题的是如何解决的
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
// 我们的原代码不变
.lg-text{
font-size: 16px;
}
</style>
<style type="text/css">
// 引入第三方样式,并添加到 third 层中
@import url(./third.css) layer(third);
</style>
<!-- <link rel="stylesheet" href="./third.css"> -->
</head>
<body>
<div class="lg-text">
Hello, CSS
</div>
</body>
</html>
由于测试方便,我们把
link
改成了@import
,不论哪种方式,可以确定的是都它们会在我们的样式代码之后加载
也就是说它们的优先级会比我们的高,按原来的所述规则,最后起作用的会是第三方的样式。
我们来看一下修改之后的效果图:
添加 @layer 最后的结果是我们的样式起作用。并且看到红色箭头所指地方出现了@layer third
字样, 这说明 @layer 生效了且改变了优先级。
@layer兼容性
看一下来自 caniuse 官网的统计,除了IE, 大部分的浏览器已经支持
@layer的语法规则
css
@layer layer-name {rules}
@layer layer-name;
@layer layer-name, layer-name, layer-name;
@layer {rules}
@layer的创建
-
具名级联层,带具体的样式规则,如
css@layer base { background-color: #f5f5f5; color: #333333; } @layer components{ font-size: 14px; } ...
-
只定义具名级联层,如
css@layer base; @layer components; // 也可以合并在一起 @layer base, components;
-
匿名级联层,如
css@layer { background-color: #f5f5f5; color: #333333; }
-
与 @import一起使用,如
css@import url(./base.css) layer(base); // 也可以是匿名的,如 @import url(./base.css) layer;
在定义的时候我们应该尽量避免创建匿名层,提高代码的可读性。
@layer的优先级
layer 的优先级与前面所说的 CSS 来源 优先级类似,也受 !important
的影响。
说一下结论:
没有设置 !important
的情况:
没有分层的样式优先级最高,其它的按照它们声明的顺序排序。第一层优先级最低,最后一层优先级最高。
我们通过一个案例来说明一下:
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
/* 未分层样式 */
.item {
color: black;
}
@layer third{
.item{
color: red;
}
}
@layer base{
.item{
color: blue;
}
}
@layer components {
.item{
color: green;
}
}
@layer utility{
.item{
color: white;
}
}
</style>
</head>
<body>
<div class="item">
Hello, CSS Layer~
</div>
</body>
</html>
上面代码运行之后的效果如下:
上述代码的样式优先级如下:
未分层样式 > utility 层 > components 层 > base 层 > third层
需要注意的一个地方就是,优先级是按照声明的先后顺序来决定的,再看一下代码:
css
@layer third, base, components, utility;
@layer base{
.item{
color: white;
}
}
@layer components{
.item{
color: white;
}
}
@layer utility{
.item{
color: white;
}
}
@layer third{
.item{
color: white;
}
}
上面代码的优先级为:
third < base < components < utility
优先级并不是以下面具体定义的先后顺序为标准,而是以声明的顺序为标准。如果没有提前声明,则会以定义的先后顺序为标准。
上面的代码都是在没有使用 !important
的情况下做的测试,下面我们再来看一下如果加上 !important
之后的优先级是怎么样的。
在设置 !important
的情况:
没有分层的样式优先级最低,其它的按照它们声明的顺序排序。第一层优先级最高,最后一层优先级最低。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style type="text/css">
/* 未分层样式 */
.item {
color: black !important;
}
@layer third{
.item{
color: red !important;
}
}
@layer base{
.item{
color: blue !important;
}
}
@layer components {
.item{
color: green !important;
}
}
@layer utility{
.item{
color: white !important;
}
}
</style>
</head>
<body>
<div class="item">
Hello, CSS Layer~
</div>
</body>
</html>
运行效果如下:
可以看出最终是 @layer third 层起作用了,与没有使用 !important
的时候优先级完全相反。
上述代码的样式优先级如下:
third层 > base 层 > components 层 > utility 层 > 未分层样式
最后总结一下 layer ,虽然 layer 的出现带来了额外的学习成本,但相比之前滥用的 !important 的时候还是给我们带来了非常大的便利,我们应该积极的去学习它。
最后总结
本篇文章比较长且有些地方难以理解,如有不明白的地方多读几遍。
文章开始我们先说明了一下以往我们所认知的 CSS 优先级 ,到后面为了更好的理解 !important 的概念及作用,我们说明了一下 CSS的三大起源 概念。
到最后我们讲解了 layer 的作用及优先级。
最后我们通过一张图来完整的看一下优先级:
并且每层中的优先级要遵循:
内联 > ID 选择器 > 类、属性和伪类选择器 > **标签选择器和伪元素选择器