经理说,都工作三年了,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 选择器 > 类、属性和伪类选择器 > **标签选择器和伪元素选择器

相关推荐
我要洋人死14 分钟前
导航栏及下拉菜单的实现
前端·css·css3
科技探秘人25 分钟前
Chrome与火狐哪个浏览器的隐私追踪功能更好
前端·chrome
科技探秘人26 分钟前
Chrome与傲游浏览器性能与功能的深度对比
前端·chrome
JerryXZR31 分钟前
前端开发中ES6的技术细节二
前端·javascript·es6
七星静香33 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
q24985969336 分钟前
前端预览word、excel、ppt
前端·word·excel
小华同学ai41 分钟前
wflow-web:开源啦 ,高仿钉钉、飞书、企业微信的审批流程设计器,轻松打造属于你的工作流设计器
前端·钉钉·飞书
Gavin_9151 小时前
【JavaScript】模块化开发
前端·javascript·vue.js
懒大王爱吃狼2 小时前
Python教程:python枚举类定义和使用
开发语言·前端·javascript·python·python基础·python编程·python书籍
逐·風6 小时前
unity关于自定义渲染、内存管理、性能调优、复杂物理模拟、并行计算以及插件开发
前端·unity·c#