经理说,都工作三年了,CSS的优先级还搞不明白……

正如我们平时口中所说的 CSS 中的优先级是用来确定当多个 CSS 规则应用于同一个元素时,哪一个规则将被应用的算法。我们通过老师或者别的同事口中得知 CSS 优先级由四个不同的级别组成,分别是:内联样式、ID 选择器、类选择器和标签选择器 。优先级的级别由高到低排列如下:

  1. 内联样式 : 散布在 HTML 标签内使用 style 属性的样式
  2. ID 选择器 : 通过 #id 语法选择的元素
  3. 类、属性和伪类选择器 :通过 .class[type="text"]:hover 等选择的元素
  4. 标签选择器和伪元素选择器 : 通过标签名(如 div、p )或 ::before 和** ::after** 等伪元素选择的元素

在应用 CSS 样式时,对于相同的样式属性,使用具有最高优先级的规则进行应用。如果两个规则具有相同的优先级,那么后面定义的规则优先级更高

例如,以下规则将会被按照优先级从高到低的顺序应用:

css 复制代码
#header {
  background-color: red;
  color: white;
}
.header {
  background-color: blue;
}
div {
  background-color: yellow;
}

在这个例子中,ID 选择器的样式具有最高优先级,其次是类选择器的样式,标签选择器的样式具有最低优先级。因此,如果存在 idheader 的元素,它将使用红色背景和白色文字。如果不存在 IDheader 的元素,但是存在 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 分别是 class2class1 ,这两个规则具有相同的优先级。

在元素使用的时候, class2class1 后面,但最后的文字颜色是 blue,这说明样式优先级与我们在元素的 class 属性使用顺序无关,只与其定义顺序有关,相同属性后面的会覆盖前面的,因为 class1 定义在 class2 后面,所以其优先级要更高。

如果单纯的理解上面的规则,确实以上的规则已经满足了我们大部分的开发需求,也没有任何问题。直到我遇到了两个关键词: !important@layer。这两个关键词的出现使得上面的规则已经不能成立,因为我们可以通过这两个词来重新定义其样式的优先级。

具体这两个关键词是如何影响优先级的,我们继续往下阅读。

本小节节选 自掘金小册《CSS 工程化核心原理与实战》,如有需要下单记得使用五折优惠码:oI3M36I4

CSS 的起源

理解 CSS 的起源是理解下面知识点关键点,CSS 中有三个起源:

  1. 创作人员(开发者)
  2. 用户(使用网页的人员)
  3. 用户代理(浏览器)

从以上字面意思我们并不难理解这三个起源的概念,默认情况下这三者的优先级是从上到下,即开发者设置的样式优先级最高,这很好理解,如果我们开发者的样式级别低,那我们写的样式会被后两者给覆盖,我们写的样式没有任何意义。

对于我们没设置值的属性,浏览器默认有自己的规则,不同的浏览器可能规则不尽相同。

!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 AttachedSelector Specificity 这两层级。对于 ContextOrder of Appearance 这两级我们先不做介绍。

结合我们所学,上面的图片我们总结简化如下:

其实文章到这里就已经基本上涵盖了我们日常开发中绝大数的场景,但事实上并没有那么简单,正如上面我们说到了 !important 的同时还提及到了另一个关键词 @layer

@layer

在具体讲解 layer 之前,我们先来说一下 @ 是干什么的。

CSS 中,存在一种特殊的规则称为 @ 规则(at-rules ),它们以 @ 符号开头,用于声明一些特殊的指令或规则,当浏览器在解析 CSS 的时候,如果遇到了 @xxx 这样的规则,会进行特殊的处理。这些规则通常在 CSS 样式表中最顶层进行声明,以区别于普通的样式规则,作用范围可能涵盖整个文档或特定的元素组。

CSS 中有两种规则,一种是我们写的普通的样式规则,如:font-size: 14px;

还有一种是以 @ 开头的,后面跟着特定的关键字的,如:@charset "UTF-8";

在开发中我们可能遇到过一些这样的规则集,如:

  1. @charset:用于声明 CSS 文件的字符编码。
  2. @import:用于导入外部的 CSS 文件,将另一个 CSS 文档中的样式引入到当前文档中。
  3. @media:用于定义媒体查询规则,即根据不同的媒介类型、视口尺寸或设备特性等,对不同的设备或场景应用不同的样式。
  4. @font-face:用于定义自定义字体,可以将自定义的字体文件加载到页面,从而在页面中使用该字体。
  5. @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>

运行效果如下,不出意外我们的样式被第三方样式库的样式给覆盖了......

解决这个问题有几种办法:

  1. 修改第三方样式库的代码,但这方式会破坏别人的代码可能会造成别的元素样式错乱,这种方式不可取
  2. 提高我们的样式的优先级,比如,把我们写的代码放在最后加载。这种方式是比较常用的方式,可以使用。但这种方式如果在某些不允许更改顺序的情况下就不能再生效。
  3. 直接在我们的代码中使用 !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的创建

  1. 具名级联层,带具体的样式规则,如

    css 复制代码
    @layer base {
       background-color: #f5f5f5;
       color: #333333;
    }
    @layer components{
       font-size: 14px;
    }
    ...
  2. 只定义具名级联层,如

    css 复制代码
    @layer base;
    @layer components;
    // 也可以合并在一起
    @layer base, components;
  3. 匿名级联层,如

    css 复制代码
    @layer {
       background-color: #f5f5f5;
       color: #333333;
    }
  4. @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 选择器 > 类、属性和伪类选择器 > **标签选择器和伪元素选择器

相关推荐
少年姜太公40 分钟前
从零开始详解js中的this(下)
前端·javascript·程序员
哑巴语天雨42 分钟前
React+Vite项目框架
前端·react.js·前端框架
初遇你时动了情1 小时前
react 项目打包二级目 使用BrowserRouter 解决页面刷新404 找不到路由
前端·javascript·react.js
乔峰不是张无忌3301 小时前
【HTML】动态闪烁圣诞树+雪花+音效
前端·javascript·html·圣诞树
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
m0_748250741 小时前
高性能Web网关:OpenResty 基础讲解
前端·openresty
前端没钱2 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
NoneCoder2 小时前
CSS系列(29)-- Scroll Snap详解
前端·css
无言非影2 小时前
vtie项目中使用到了TailwindCSS,如何打包成一个单独的CSS文件(优化、压缩)
前端·css
我曾经是个程序员3 小时前
鸿蒙学习记录
开发语言·前端·javascript