现代 CSS

初探 CSS 的级联层(@layer)

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

稍微接触过 CSS 的 Web 开发者,级联(层叠)和继承 是 CSS 领域中的一个非常重要的概念。该功能模块在 W3C 规范中也经历了多个版本的迭代,至今天已有多个版本了(CSS2.2Level3Level4Level5),这也足以说明其在 CSS 中的重要性。

对于 Web 者而言,在编写 CSS 时,必须仔细考虑如何编写和组织代码。特别是在一个大型项目或多人协作开发的项目中,级联很容易给项目开发造成不少的障碍,比如说代码相互覆盖,选择器权重造成样式的冲突等。为了在这些情况下重新获得对级联(层叠)的控制,CSS Cascading and Inheritance Level5 规范中新增了一个新的 CSS 特性,即 @layer 规则(一个新的 @ 规则,也就是大家所说的 at-rule 规则),该规则让 CSS 有了层的概念,可以将 CSS 完全封装起来,以便导入。这意味着模块、脚本或其他任何导入你的项目的东西都可以有完全独立的 CSS,从而解决了样式相互覆盖的老问题,也解决了选择器权重造成的样式冲突问题。它还可以让我们更灵活地使用用自定义导入语句,将CSS添加到到页面中。

接下来,让我们来看看它们是什么,我们如何使用它们,以及它们带来了什么好处。

先简单地了解一下CSS 的级联

从《图解CSS:CSS层叠和继承》一文中,可以得知,CSS级联,也被称为 CSS 层叠,正好对应着 CSS(Cascade Style Sheets)中首字母,即 C,也就是 Cascade

主要用来解决应用于同一个元素的CSS冲突的算法,即浏览器通过它来决定将哪些 CSS 样式规则应用到一个元素上。比如下面这个示例:

<!-- HTML -->
<input type="password" id="password" style="color: blue;">

/*CSS*/
input {
    color: grey;
}

input[type="password"] {
    color: hotpink !important;
}

#password {
    color: lime;
}

示例中的 <input>style属性,CSS中的样式代码,甚至用户代理客户端(浏览器)给密码框的默认样式都是用来指定其文本颜色的。只不过,最终密码输入框的文本颜色是粉红色(即hotpink),也就是说,带有 !important 的样式规则最终获胜:

为了决定哪个声明(CSS样式规则)会“获胜”(从而被应用到元素上),级联提供了相应的算法。了解级联算法有助于帮助我们理解浏览器是如何解决样式规则冲突,也就是浏览器决定哪个样式规则运用到元素上。级联算法在不同的规范中有不同的描述,在 Level 5中提供了六个不同的级别。在不考虑级联层的情况下,其标准如下:

这些标准的优先级从高到低排列,并且一个接一个地检查,直到确定一个获取的声明。如果在较高的标准上不能确定哪一个属性声明会获胜,级联将转到下一个标准。比如下图所示:

有关于级联和选择器权重更深入的介绍,还可以阅读下面几篇文章:

注意,在 CSS 选择器 Level 4中新增了一些新的伪类选择器,比如 :is():not():has():where()等。他们对选择器权重有着额外的计算方式,其中 :is():not()或者:has() 伪类选择器的权重被其选择器列表参数中最高的权重所取代;:where()伪类选择器权重计算成0。如果你对 CSS 选择器 Level 4 的一些选择器感兴趣的话,可以阅读《初探CSS 选择器Level 4》一文。

控制 CSS 的级联

你可能已经感觉到了 CSS 级联的复杂性。不过也别太担心。因为我们在编写 CSS 代码时,主要是将我们的 CSS 放在一个相同的来源上,即 Author Origin(开发者编写的 CSS 样式)。因此,我们最终会使用选择器权重和顺序作为控制级联的方法。这样一来,时常会碰到:

  • 使用较高权重的选择器来防止你的代码被后面的代码(或别人的代码)覆盖。但这也会引起另一个不良的现象,可能会在代码中新增很多带有 !important 的样式规则,这本身就会引起更多的问题,比如 !important 在 CSS 样式表中随处可见,需要覆盖的时候难以被覆盖
  • 使用较低权重的选择器又很容易被后面的代码(或别人的代码)覆盖。比如你在引入第三方代码库或组件时,自己的代码可能被覆盖

这两个现象也是编写CSS代码,特别是在一个大型项目或多人协作的项目中常出现。也正因为如此,很多初学 CSS 的开发者,觉得 CSS 很烦人,很难维护。为了大家能更好的编写 CSS和维护CSS代码,这些年来整个社区的开发者一直都在致力于提供各种方法来避免这些现象的出现。比如 BEMITCSSOOCSSCSS-in-JSCSS ModulesCSS Scoped等。这些方法论主要倚重于以下两个方面:

  • 以这样的方式构建你的代码,创造某种逻辑顺序,使之适用于大多数情况
  • 依靠类来保持选择器的权重尽可能的低

比如 ITCSS,他就分多个层来组织和管理 CSS的级联:

有关于 ITCSS 更详细的介绍可以阅读《ITCSS: Scalable and Maintainable CSS Architecture》一文。

虽然社区有很多方法来帮助我们编写CSS和掌握CSS的级联,但这些方法并不能百分之百的解决级联给我们带来的麻烦。主要还是:

  • 由于源码顺序仍然起着决定性的作用,所以顺序带来的覆盖和冲突依旧未真正的解决(“所谓的顺序”并未真正的执行)
  • 选择器权重仍然比层的顺序(源码顺序)更重要

也就是说,要真正的解决级联带来的这些问题,还是需要依靠 CSS 的级联层,也就是 CSS 的 @layer 规则。

级联层的简介

在真正进入级联层(@layer)的世界中之前,我们首先要感谢 @TerribleMia设计了级联层,给我们带来这么优秀的 CSS 功能。最初的设计可以参阅《Cascade Layers Explainer》!

级联层是 CSS Cascading and Inheritance Level 5 规范新增的一项 CSS 特性,由@TerribleMia 主导和推进。规范中是这样描述级联层的:

Declarations within each origin and context can be explicitly assigned to a cascade layer. For the purpose of this step, any declaration not assigned to an explicit layer is added to an implicit final layer.Cascade layers (like declarations) are ordered by order of appearance. When comparing declarations that belong to different layers, then for normal rules the declaration whose cascade layer is last wins, and for important rules the declaration whose cascade layer is first wins. -- CSS Cascading and Inheritance Level 5

大致意思就是说,“每个来源(Origin)和上下文(Context)中的 CSS规则都可以被明确的分配到指定的级联层(Layer)内,而没有显式被分配到级联层的 CSS 样式规则则会被添加到一个隐式的级联层中。级联层就像我们写CSS的规则相似,是按照其在代码中出现的先后顺序排列的,排在越后面的级联层权重越大。当比较不在相同级联层的声明时(选择器权重,样式规则都一样),那么对于正常的规则(不带!important样式规则),级联层在最后的声明获胜,而对于重要的规则(带有!important的规则),级联层在前面的声明获胜”。

也就是说,开发者可以通过级联层(使用 @layer规则),将你的 CSS 分成若干个层。这样一来,来源于用户(User Origin)和开发者(Author Origin)的 CSS 规则,开发者可以有权力来平衡他们。简单地说:

级联层提供了一种结构化的方式来组织和平衡单一来源中的CSS规则,最终决定谁获胜!

由于 CSS 的级联层在CSS级联中有着独特的地位,使用它有一些好处,使开发者对级联有更多的控制。CSS的级联层一般位于“Style 属性”(Style Attribute)和 CSS 选择器权重(Specificity)之间,即:

我们接下来跟着规范中提供的一些案例来深入了解 CSS 的级联层,顺便一探其究竟~!

特别声明: 我们这里所说的层和定位中的堆栈层叠(用z-index控制的层)不是同一个概念,千万别混淆了!通过 z-index 分层是指在视觉上控制盒子堆叠顺序(Z轴的位置),级联层是关于构造你的 CSS 代码和控制 CSS的级联(层叠)。有关于控制盒子堆叠更多的介绍,可以阅读《聊聊CSS中的层叠相关概念》和《Web布局:CSS定位和层叠控制》。

我们现在可以使用 CSS 的级联层?

到写这篇文章为止,虽然 CSS 的级联层已发布了 WD 版本,但并不能代表规范中所描述的一切都是一尘不变的,有些功能特性有可能会随着后续的演进有所变化。但对于我们学习它不会有任何的影响。因为在主流浏览器中都努力增加级联层的实验支持。这仍然是实验性的支持,但相对而言浏览器对该特性的支持进度已经非常的好了

虽然 Caniuse 上还是一片飘红,但我们在 Google Chrome(版本 97.0.4690.2(正式版本)canary )、Firefox(94.0+)和 Safari Technology Preview(Safari 15.4, WebKit 17613.1.6.1)等浏览器中开启相应的标记,可以看到 @layer 的使用效果。你需要先将相应浏览器升级到这些版本(或更高版本),然后按下面的方式开启相应的标记。

Google Chrome Canary

打开 Canary 浏览器,在 URL 栏中输入 chrome://flags/ ,在打开的页面的搜索框中搜索 #enable-cascade-layers,在对应的 “Enable CSS Cascade Layers” 对应的下拉框中,选择 Enabled 选项,然后重启浏览器即可:

Firefox

如果你使用的是 Firefox,先在 URL 栏中输入 about:config,在打开页面的搜索框中搜索 layout.css.cascade-layers.enabled,将其值设置为 true之后重启浏览器:

Safari Technology Preview

Safari Technology Preview(这里也简称 Safari 吧),他的开启方式略有不同,可以直接从浏览器的菜单栏中开启。你可以按照 “Develop” » “Experimental Features” » “CSS Cascade Layers”。选中“CSS Cascade Layers” 选项即可:

你可以使用下面这个简单的示例来测试

<!-- HTML -->
<h1 class="title" id="title">CSS @layer</h1>

/* CSS */
@layer base {
    #title {
        color: red;
    }
}

@layer theme {
    .title {
        color: blue;
    }
}

@layer component {
    h1 {
        color: orange;
    }
}

如果你使用上面所说的浏览器,看到h1的文本颜色是orange,那么恭喜你,你可以开始体验 CSS 级联层(@layer)了。如果不是,那你得重新按照上面的步骤开启相应的标记:

不支持的浏览器,看到的文本颜色是白色的。

CSS 级联层的使用

我们还是由简入深,看看 CSS 级联层是如何使用的。先从创建级联层开始吧!

创建级联层

我们可以显式使用 CSS 新增的 @(at-rule)规则来创建级联层,即 @layer。使用 @layer 规则可以用不同的方式来创建级联层。最简单的一种方式:

@layer myLayer {
    /* CSS Code ... */
}

@layer 规则后面的 myLayer 是新创建的级联层名称,可以在大括号{}内放置你与myLayer相关的CSS样式规则。可以像平时编写 CSS规则一样,在这里面放置任何你想要的 CSS规则,比如:

@layer myLayer {
    h1 {
        color: orange;
        animation: slider-left 10s linear alternate infinite;
    }

    @media screen and (max-width: 760px) {
        h1 {
            color: #f36;
        }
    }

    @keyframes slider-left {
        from {
            translate: 100% 0;
        }
        to {
            translate: -100% 0;
        }
    }
}

效果如下

这种创建级联层的方式有一个特点:使用 @layer 定义了带有一个名称的级联层,并立即给该级联层分配了 CSS 样式!。我们再来看第二种创建级联层的方式。

同样在 @layer 规则紧跟一个或多个级联层名称(多个级联层名需要用逗号,分隔开),但不立即分配样式,然后再像上面示例一样,重新使用 @layer + 级联层名称 {} 方式放置 CSS规则。比如:

/* 建立多个级联层 */
@layer framework, override;

@layer framework {
    @keyframes fadeIn {
        from {
            opacity: 0
        }
        to {
            opacity: 1
        }
    }

    h1 {
        color: orange;
    }
}

@layer override {
    @keyframes fadeIn {
        from {
            opacity: 0;
            color: #f36;
        }
        to {
            opacity: 1;
            color: lime;
        }
    }

    h1 {
        color: yellow;
    }
}

.title {
    animation: fadeIn 2s linear alternate infinite;
}

效果如下

你可能从上面的效果中已经发现了,放在后面的 override 级联层中的 CSS 样式优先级更高。当然,你也可以一个一个地定义多个级联层,比如:

@layer framework {
    /* CSS Code ...*/
}

@layer override {
    /* CSS Code ...*/
}

也可以使用带有layer关键词(这个是指级联层的名称)或layer()函数的 @import 创建一个级联层:

@import(reset.css) layer(reset);
@import url(reset.css) reset;

比如上面的代码创建了一个名为 reset 的级联层。

我们还可以使用 @layer 创建一个不带有级联层名称的层,比如:

@layer {
    /* CSS Code ...*/
}

使用这种方式,我们创建了一个匿名的级联层。甚至还可以像下面这样创建一个你明级联层:

@layer;

只不过这样创建的是一个无用的级联层,因为:

  • 它一开始就没有内容(CSS样式规则)
  • 不能附加额外的内容(不能被引用)

除了上述提到的方式之外,还有一种新的创建级联层的方式,只不过这种方式还在研究中,即 通过<link>标签上的一个属性来创建级联层。它的方式可能会下面这样:

<link rel="stylesheet" href="reset.css" layer="reset">

也有可能使用<style>创建:

<style layer="reset">
    *,
    *::before,
    *::after {
        box-sizing: border-box;
    }
</style>

这些都还只是探究中,并未纳入到规范中,如果你对这方面的讨论感兴趣的话,可以点击《Provide an attribute for assigning a <link> to a cascade layer》,查阅读相关讨论。当然,你自己也可以参与讨论。

到目前为止,创建级联层的方式主要有:

  • 使用一个@layer块规则,将其子样式规则分配到该级联层
  • 使用 @layer 语句规则,声明一个命名的级联层,但不分配任何样式规则
  • 使用带有layer关键词(也可以是级联层的名称<layer-name>)或layer()函数的@import规则,将导入文件的内容分配到该级联层

匿名级联层

这里特意把匿名级联层拿出来单独说一下。

当一个@layer规则省略了它的级联层名称(<layer-name>),或者一个 @import 规则使用了layer关键词(没有提供<layer-ame>),它的级联层名称(Layer Name)获得了一个唯一的匿名参数(Anonymous Segment);因此它不能从外部被引用。

因此,匿名级联层声明的每一次出现都代表了一个独特的级联层。

多个匿名级联层规则将其样式放入不同的级联层中,因为每一次出现都引用了一个不同的匿名级联层名称:

@layer { 
    /* 匿名级联层 layer 1 */
}

@layer { 
    /* 匿名级联层 layer 2 */ 
}

在一个单一的匿名级联层内,具有相同名称的子级联层指的是同一个级联层,因为它们共享同一个匿名的父级联层:

@layer {
    @layer foo { 
        /* 匿名级联层 layer 1 */ 
    }

    @layer foo { 
        /* 也是匿名级联层 layer 1 */ 
    }
}

而在独立的匿名级联层中,具有相同名称的子级联层指的是不同的级联层,因为它们有不同的匿名级联层:

@layer {
    @layer foo { 
        /* 匿名级联层 layer 1 */ 
    }
}

@layer {
    @layer foo { 
        /* 匿名级联层 layer 2 */ 
    }
}

一个没有 <layer-name> 的级联层不会提供任何外部钩子来重新排列或添加样式。

虽然这可能只是为了简洁方便,但它也可以被团队用作强制组织惯例的方式(该级联层的所有代码必须定义在同一个地方),或者被想要合并和隐藏一组内部“私有”级联层的库所使用,他们不希望暴露给开发者操作。

/* bootstrap-base.css */
/* 围绕每个子文件的未命名的父级联层 */
@import url(base-forms.css) layer;
@import url(base-links.css) layer;
@import url(base-headings.css) layer;

/* bootstrap.css */
/* 内部级联层名称被隐藏起来,不被访问,归入 base 级联层 */
@import url(bootstrap-base.css) layer(base);

/* author.css */
/* 开发者可以访问bootstrap.base 级联层,但不能访问未命名的级联层 */
@import url(bootstrap.css) layer(bootstrap);

/* 给bootstrap级联层添加额外的样式 */
@layer bootstrap {
    /* ... */
}

就个人建议而言,匿名级联层应该尽可能,甚至是不用。因为匿名级联层在操作上是很不灵活的,在改变匿名级联层顺序,给匿名级联层附加样式都是不易的。

管理级联层的顺序

级联层是按照它们第一次被声明的顺序来排序的

前面我们提到过,在还没有 CSS 级联层的时候,一般都是使用一些方法论来组织我们的CSS。拿 ITCSS 来说吧,把ITCSS每个层用一个单独的 .css文件来组织,并且会按下面这样的顺序一排序:

@import 'setting.css'
@import 'tool.css'
@import 'generic.css'
@import 'element.css'
@import 'object.css'
@import 'component.css'
@import 'utilities.css'

换成 CSS 级联层的话,可以像下面这样创建七个级联层:settingtoolgenericelementobjectcomponentutilities

@layer setting {
    /* 创建第一层,并且命名为 setting */
    :root{
        --base-font-size: 1rem;
        --base-line-height: 1.5;
        --base-text-color: #fff;
        --base-background-color: #557;
    }
}

@layer tool {
    /* 创建第二层,并且命名为 tool */
    /* 
        如果使用SCSS,一般是一些混合宏放置在这一层 
        @mixin box-size($width:100%, $height:100%) {
            width: $width;
            height: $height;
        }
    */
}

@layer generic {
    /* 创建第三层,并且命名为 generic */
    *,
    *::before,
    *::after {
        box-sizing: border-box;
    }

}

@layer element {
    /* 创建第四层,并且命名为 element */
    body {
        color: var(--base-text-color);
        background-color: var(--base-background-color);
    }
    h1 {
        font-size: clamp(var(--base-font-size), var(--base-font-size) + 8vw, var(--base-font-size) * 4);
    }
}

@layer object {
    /* 创建第五层,并且命名为 object */
    /* EMPTY */
}

@layer component {
    /* 创建第六层,并且命名为 component */
    .title {
        animation: bounce 2s ease infinite;
        transform-origin: center bottom;
        color: orange;
    }
}

@layer utilities {
    /* 创建第七层,并且命名为 utilities */
    @keyframes bounce {
        from,
        20%,
        53%,
        to {
            animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
            transform: translate3d(0, 0, 0);
        }

        40%,
        43% {
            animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
            transform: translate3d(0, -30px, 0) scaleY(1.1);
        }

        70% {
            animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
            transform: translate3d(0, -15px, 0) scaleY(1.05);
        }

        80% {
            transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
            transform: translate3d(0, 0, 0) scaleY(0.95);
        }

        90% {
            transform: translate3d(0, -4px, 0) scaleY(1.02);
        }
    }
}

效果如下

上面示例的级联层按照他们在代码中出现的顺序,层层递进:

  • ①:setting
  • ②:tool
  • ③:generic
  • ④:element
  • ⑤:object
  • ⑥:component
  • ⑦:utilities

级联层是按照他们第一次声明的顺序进行排序的,越排在后面的优先级越高。当你重新使用一个级联层名称时,指的是级联层名称相同,只是排在了后面。比如我们在上面的代码最后面又使用 @layer 创建了一个 generics 级联层:

@layer setting {
    /* 创建第一层,并且命名为 setting */
}

@layer tool {
    /* 创建第二层,并且命名为 tool */
}

@layer generic {
    /* 创建第三层,并且命名为 generic */
}

@layer element {
    /* 创建第四层,并且命名为 element */
}

@layer object {
    /* 创建第五层,并且命名为 object */
}

@layer component {
    /* 创建第六层,并且命名为 component */
}

@layer utilities {
    /* 创建第七层,并且命名为 utilities */
}

@layer generics {
    /* 重新创建了一个 generics 级联层,并放在最后面 */
    @media only screen and (max-width: 760px) {
        .title {
            color: #f36;
            text-shadow: 1px 1px 1px rgb(255 255 255 / .5);
        }
    }
}

这个重新创建的级联层 generics 中的样式会附加到已经存在的 generics 级联层中。级联层的顺序保持不变,因为第一次创建级联层时就已经决定了它们的顺序。

把上面示例简化一下,比如下面这个示例:

<!-- HTML -->
<h1 class="title" id="title">Managing Layer Order</h1>

/* CSS */
@layer base {
    h1 { /* 选择器权重是 0, 0, 1*/
        color: orange;
    }
}

@layer theme {
    h1 { /* 选择器权重是 0, 0, 1*/
        color: lime;
    }
}

@layer base {
    .title { /*选择器权重是 0, 1, 0*/
        color: yellow;
    }
}

这个示例使用 @layer 创建了 basetheme两个级联层,由于theme排在第一个base的后面,theme的优先级高于base。同时,在theme后面又追回了base级联层,而且该级联层中的.title选择器的权重高于h1的选择器权重。按照选择器权重来决定样式的话,应该是.title的样式被运用,但因为新增的base级联层和最开始创建的base级联层会合并在一起,它相当于:

@layer base {
    h1 {
        color: orange;
    }

    .title {
        color: yellow;
    }
}

@layer theme {
    h1 {
        color: lime;
    }
}

由于级联层theme优先级高于级联层base,选择器.titleh1选中的都是同一个元素,即使.title选择器权重高于h1选择器权重也无济于事。浏览器最终会选择级联层theme中运用于h1的样式规则。你最后将看到的h1的文本颜色是lime

这就是 CSS 级联层的优势。

上面这两个示例演示了,在CSS中重新使用一个相同的级联层名称时,级联层的顺序会保持不变,这样一来,我们可以使用 @layer 语句的规则先预设级联层的顺序,然后在后面将所需的样式规则附加到对应的级联层中。

/* 预设级联层的顺序,并且相邻级联层之间有逗号分隔 */
@layer setting, tool, generic, element, object, component, utilities

@layer setting {
    /* 附加到级联层 setting 中的 CSS */
}

@layer tool {
    /* 附加到级联层 tool 中的 CSS */
}

@layer generic {
    /* 附加到级联层 generic 中的 CSS */
}

@layer element {
    /* 附加到级联层 element 中的 CSS */
}

@layer object {
    /* 附加到级联层 object 中的 CSS */
}

@layer component {
    /* 附加到级联层 component 中的 CSS */
}

@layer utilities {
    /* 附加到级联层 utilities 中的 CSS */
}

使用@layer语法规则(建议使用单行)预设好级联层顺序,然后在相应的级联层添加样式。在相应级联层添加样式时,他的顺序就变得不再重要了。

/* Case 1*/
@layer base, theme;

@layer base {
    h1 {
        color: orange;
    }
}

@layer theme {
    h1 {
        color: lime;
    }
}

/* Case 2*/
@layer base, theme;

@layer theme {
    h1 {
        color: lime;
    }
}

@layer base {
    h1 {
        color: orange;
    }
}

这两种不同的写法,最终结果是一样的,主要是因为 @layer base, theme; 已经决定了级联层的优先级。

CSS 中的级联层 和 级联

CSS 级联层的出现,改变了以前的级联算法。在原有的级联顺序(“来源与重要性” ⇝ “上下文” ⇝ “Style属性” ⇝ “权重” ⇝ “源码顺序”)的 “Style属性” 和 “权重” 之间插入了“级联层”(“来源与重要性” ⇝ “上下文” ⇝ “Style属性” ⇝ “级联层” ⇝ “权重” ⇝ “源码顺序”)。即 级联层位于权重和源码顺序之上,因为,在级联算法中,级联层比权重和源码顺序更具高优先级

级联在评估级联层获胜的标准其实也参照了源码顺序的工作原理:最后一个获胜!也就是说,最后声明的级联层优先级要高于前面声明的级联层:

级联层(像声明的CSS规则)是按照源码顺序排列的。当比较属于不同层的声明时,那么对于正常的规则来说,级联层在最后的声明获胜

拿前面的示例解释这句话:

<!-- HTML -->
<h1 class="title" id="title">CSS @layer</h1>

/* CSS */
@layer base, theme, component;

@layer base {
    #title {
        color: red;
    }
}

@layer theme {
    .title {
        color: blue;
    }
}

@layer component {
    h1 {
        color: orange;
    }
}

我们创建了三个级联层,它们的顺序是:

  • ①:base
  • ②:theme
  • ③:component

这三个级联层中的 CSS 样式规则都是用来给 <h1> 元素设置一个文本颜色,他们不同之处是使用了不同的选择器:

  • base 级联层中使用了 ID 选择器(#title),选择器权重是 1, 0, 0
  • theme 级联层中使用了类选择器(.title),选择器权重是 0, 1, 0
  • component 级联层中使用了元素选择器(h1),选择器权重是 0, 0, 1

稍微对CSS级联有点了解的同学都知道,如果没有级联层的话,不管源码顺序如何,最终获胜的会是 #title,元素<h1>的文本颜色是red。但有了级联层之后,级联算法就不同了。就该示例来说,component 级联层最后被定义,根据@layer的单行语法规则,可以获知它优先级都要高于themebase级联层(theme级联层优先级高于base级联层),此时,虽然附加于component中的h1选择器权重最低,但最终胜出的还是它。这是因为,一旦通过级联按照出现在源码的顺序确定了获取的声明,级联是不会再检查CSS规则(附于级联层中的CSS规则)的选择器权重和出现在源码中的顺序。其中原理是级联层是级联的一个独立的,等级更高的标准

注意,这并不意味着有了 CSS 级联层之后,就意味着 CSS 选择器权重和源码顺序就不再重要了。在同一个级联层中,这两个标准仍然重要。只不过在级联层之间比较CSS规则时,这两个标准可以被忽略

简单地小结一下

如果你只要简单的了解一下 CSS 级联层(@layer)如何使用,那么你阅读到这里就可以了。你只需要知道:

  • 通过级联层,可以把你的 CSS 划分成 N 个层(这个N是由你的喜好来决定的)
  • 在使用 @layer 规则创建一个级联层时,你也要确定级联层的顺序,建议使用 @layer 单行语法规则来预设级联层的顺序
  • 使用 @layer 可以创建带有名称的级联层,也可以创建匿名的级联层,它们不同的之处是,匿名级联层是在 @layer 规则和 {} 之间没有显式给级联层命名,即 @layer{}
  • 使用 @layer 创建级联层时,后面不跟任何层名和样式块,该语句是正确的,但是一个无效的级联层,因为你无法给其附加任何样式规则
  • 重新使用级联层的名称将会附加到已经创建的级联层上,且不会改变级联层的顺序
  • 级联层的优先级也遵循源码顺序的标准,最后创建的级联层优先级最高,即 最后的级联层获胜
  • 级联在评估权重和源码顺序之前会先评估级联层。因为级联层是级联中的一个独立的,等级更高的标准。CSS权重和源码顺序在级联层之间比较 CSS 规则时,可以被忽略;但在同一个级联层中,权重和源码顺序仍然很重要

你是否已经体会到了级联层给 CSS 带来的魅力了。如果你还想更深入的了解级联层,那请跟我继续往下。

CSS 级联层中的一些细节

上面介绍的只是级联层中最基础的部分:

  • 如何使用 @layer 创建级联层
  • 如何管理级联层的顺序
  • 级联层的作用

事实上,级联层中还有一些细节需要我们进一步的了解。我们先从无级联层样式开始吧!

无级联层样式的优先级

虽然 @layer 的出现,可以帮助我们对 CSS 进行分层,但也有可能在编写 CSS 的时候,有些 CSS 的规则不会附加到任何级联层中。比如:

/* Case 1*/
* {
    box-sizing: border-box;
}

@layer base, theme;

@layer base {
    h1 {
        color: red
    }
}

@layer theme {
    h1 {
        color: orange;
    }
}

/* Case 2*/
@layer reset, base, theme;

@layer reset {
    * {
        box-sizing: border-box;
    }
}

body {
    width: 100vw;
}

h1 {
    margin: 0;
}

@layer base {
    h1 {
        margin-bottom: 1rem;
    }
}

@layer theme {
    h1 {
        margin-bottom: 2rem;
    }
}

/* Case 3*/
@layer base, theme;

@layer base {
    h1 {
        color: red;
    }
}

@layer theme {
    h1 {
        color: orange;
    }
}

h1 {
    color: yellow;
}

/* Case 4*/
* {
    box-sizing: border-box;
}

@layer base, theme;

h1 {
    color: red;
}

@layer base {
    h1 {
        color: orange;
    }
}

h1 {
    margin-bottom: 2rem;
}

@layer theme {
    margin-bottom: 1rem;
}

.title {
    color: yellow;
    border-bottom: 1px solid;
    padding-bottom: .25rem;
}

正如上面代码所示,未附加到级联动中 CSS 规则,有可有会在级联层前面、有可能会在级联层之间,也有可能会在级联层之后。我们不管他们出现在哪,统一把这些 CSS 规则称为 无级联层样式,也称 非分层样式

没有在级联层中定义的样式将被收集到一个隐含级联层(Implicit Layer)。那么问题来了,这个隐含级联层的优先级是什么,是第一,还是最后?

注意,隐含级联层(Implicit Layer)不同于匿名级联层(Anonymous Layer),匿名级联层是需要使用 @layer 规则创建一个不带有名称的级联层,而隐含级联层和 @layer 无任何关系,它只是没有在级联层中样式规则集合的一个统称!

先来看第一种情况带来的结果:

<!-- HTML -->
<h1 class="title" id="title">Unlayered Styles</h1>

/* CSS */
h1 {
    color: orange;
}

@layer base, theme;

@layer base {
    h1 {
        color: yellow;
    }
}

@layer theme {
    h1 {
        color: lime;
    }
}

最终文本颜色是orange,也就是在级联层 basetheme之外的 h1对应的样式规则获胜:

接着看第二种情况,在上例基础上,把放置在级联层外的 h1 {color: orange}规则移到 basetheme 级联层之间:

@layer base, theme;

@layer base {
    h1 {
        color: yellow;
    }
}

h1 {
    color: orange;
}

@layer theme {
    h1 {
        color: lime;
    }
}

结果和第一种情况是一样的,h1 文本颜色是orange

再来看第三种情况,把放置在级联层外的 h1 {color: orange} 规则移到所有级联层之后:

@layer base, theme;

@layer base {
    h1 {
        color: yellow;
    }
}

@layer theme {
    h1 {
        color: lime;
    }
}

h1 {
    color: orange;
}

你可能已经猜到结果了,是的,h1 的文本颜色依旧是orange

最后一种情况,在级联层 basetheme 前后和中间都有设置h1元素的样式,比如:

h1 {
    color: #f36;
}

@layer base, theme;

@layer base {
    h1 {
        color: yellow;
    }
}

h1 {
    color: #09f;
}

@layer theme {
    h1 {
        color: lime;
    }
}

h1 {
    color: orange;
}

在级联层 basetheme 之外不同位置有三个 h1 选择器设置了 color 值,最终出现在最后面的 h1 {color: orange} 获胜,文本颜色也是 orange。我们稍微调整一下,把三个选择器权重调一调:

#title {
    color: #f36;
}

@layer base, theme;

@layer base {
    h1 {
        color: yellow;
    }
}

.title {
    color: #09f;
}

@layer theme {
    h1 {
        color: lime;
    }
}

h1 {
    color: orange;
}

此时,选择器#title获胜,h1文本颜色相应调整为#f36

你可能已经发现了,隐含级联层(即由未分层的样式规则的集合)被放置在所有级联层的最后面。相当于:

@layer base, theme, unlayered;

@layer base {
    h1 {
        color: yellow;
    }
}

@layer theme {
    h1 {
        color: lime;
    }
}

@layer unlayered {
    #title {
        color: #f36;
    }
    .title {
        color: #09f;
    }
    h1 {
        color: orange;
    }
}

注意,把这个unlayered级联层看作是一个隐含级联层(实际上在上面的代码他是一个显式的级联层),这里只是来模拟未分层的样式的优先级。在 unlayered 级联层中有分别有 #title.titleh1 三个选择器,最终是#title获胜,因为他权重最高。这些是符合我们前面所介绍的级联优先级算法的。

说这么多(用这么多个示例),只是想告诉大家,未分层的样式规则(不管样式规则在源码顺序)会统一分配到一个隐含级联层中,并且这个隐含级联层始终在最后面,其优先级也最高

把上图中级联算法中的级联层单独拿出来对比:

特别声明,最早隐藏级联层是放置在所有级联层最前面,优先级最低,只不过后来 CSS 工作组还是决定将隐含层放在所有级联层最后面,优先级最高,到目前为止,浏览器也是这样实现的(也就是上面示例所展示的效果)。有关于这方面的讨论可以阅读 Github上的《Reconsider placement of unlayered styles, for better progressive enhancement?》相关讨论!同时还有另一个Issues:《Allow authors to explicitly place unlayered styles in the cascade layer order》在讨论:“在未来,我们可能会获得控制隐含级联层位置的能力,即可以控制隐含层在级联层中的顺序”!

级联层的嵌套

CSS 的级联层是可以被嵌套的,即 @layer 规则中嵌套 @layer 规则:

@layer myLayer {
    @layer mySubLayer {

    }
}

比如下面这样的一个示例:

/* 创建第一层,并且命名为 base */
@layer base {
    h1 {
        color: orange;
    }
}

/* 创建第二层,并且命名为 framework */
@layer framework {
    /* 第二层里的第一层,并且命名为 base */
    @layer base {
        h1 {
            color: lime;
        }
    }
    /* 第二层里的第二层,并且命名为 theme */
    @layer theme {
        h1 {
            color: yellow;
        }
    }
}

h1 的文本颜色是 yellow

在这个例子中,有两个外层:

  • ①:base
  • ②:framework

其中framework级联层又包含了两个级联层:

  • ①:base
  • ②:theme

示例中有两个级联层命名为 base,但这两个级联层是不同的,即 framework.base 级联层(指的是级联层framework内部的级联层base)和 外部的级联层 base 是不同的。也就是说,这两个 base 级联层并不冲突,因为第二个 base级联层只是 framework 级联层内部的一部分。即如果有外部级联层的话,级联层的名字范围是其外层的级联层。简单地说,嵌套在某个级联层中的级联层,他们的作用域是在其父级联层中。它们之间的隶属关系就有点像 DOM 树的样子:

  • ①:base
  • ②:framework
    • ②~①:base
    • ②~②:theme

或作为一个带有嵌套标识的扁平化列表:

  • ①:base
  • ②:framework.base
  • ③:framework.theme

我们再花点时间 CSS 级联算法是怎么处理级联层嵌套的。首先,同一个嵌套层中的级联层也遵循前面所说的级联算法,最后面的级联层获胜

@layer framework {
    @layer base {
        h1 {
            color: lime;
        }
    }

    @layer theme {
        h1 {
            color: yellow;
        }
    }
}

h1的文本色是yellow

@layer framework {
    @layer theme {
        h1 {
            color: yellow;
        }
    }

    @layer base {
        h1 {
            color: lime;
        }
    }
}

h1 的文本色是lime

如果跳出父级联层的话,CSS 的级联算法先会比较最外层的级联层顺序,然后再比较内部的级联层顺序,比如:

@layer base {
    h1 {
        color: orange;
    }
}

@layer framework {
    @layer base {
        h1 {
            color: lime;
        }
    }

    @layer theme {
        h1 {
            color: yellow;
        }
    }
}

h1文本颜色为yellow

@layer framework {
    @layer base {
        h1 {
            color: lime;
        }
    }

    @layer theme {
        h1 {
            color: yellow;
        }
    }
}

@layer base {
    h1 {
        color: orange;
    }
}

h1文本颜色为orange

正如你所看到的效果,CSS 级联算法先会根据最外层的级联层顺序做优先级评估,同样是最后一个级联层获胜。如果最后一个级联层里面还有嵌套的级联层,那么再比较嵌套的级联层顺序,同样是最后一个级联层获胜。

相当于把嵌套的级联层排平,然后按照级联层顺序来做评估。比如上面两个示例,排平嵌套的级联层,他们的顺序分别是:

  • ①:base
  • ②:framework.base
  • ③:framework.theme,嵌套在framework级联层中的theme级联层获胜

另一个,只是所base级联层移到最后:

  • ①:framework.base
  • ②:framework.theme
  • ③:base,外层的base级联层是最的一个了,因此它获胜

在介绍未分层样式纳入到一个隐含级联层,具有最高优先权(因为会放在所有级联层的后面)。那么在嵌套级联层中,外面的级联层可有会有CSS样式规则不会附加到内嵌套的级联层中。比如:

@layer base {
    h1 {
        color: orange;
    }
}

@layer framework {
    h1 {
        color: lime;
    }

    @layer base {
        h1 {
            color: yellow;
        }
    }

    @layer theme {
        h1 {
            color: #f36;
        }
    }
}

这个时候,CSS 级联算法也遵循前面所说的,隐含级联层优先级最高,在嵌套的级联层也是如此。因此,该示例你看到的 h1 文本颜色是 lime

如果我们用树形结构来描述上面示例级联层顺序,你就能知道为什么文本颜色是 lime了:

  • ①:base
  • ②:framework
    • ②~①:base
    • ②~②:theme
    • ②~③:unlayered

把它们拍平的话,像下面这样:

  • ①:base
  • ②:framework.base
  • ③:framework.theme
  • ④:framework级联层中的隐含级联层,你可以看作是 framework.unlayered

这样是不是好理解得多了。

在嵌套级联层中,也可以使用 @layer单行语法规则对嵌套的级联层顺序做预设,然后在相应的级联层中附加CSS规则:

/* CSS */
@layer base {
    h1 {
        color: orange;
    }
}

@layer framework {
    @layer base, theme;

    @layer theme {
        h1 {
        color: yellow;
        }
    }

    @layer base {
        h1 {
        color: lime;
        }
    }
}

就这个示例而言,framework 级联层中的嵌套级联层的顺序已经被@layer base, theme语句预设了,因此执着下来的 @layer base {}@layer theme{} 谁前谁后已无济于事了。最终都是 framework.theme最有更高的优先权。因此,你看到的文本颜色是yellow

另外,我们同样可以在嵌套的级联层中使用相同的级联层名称,对应的样式规则也会合并到前面已有的级联层中,比如:

@layer framework {
    @layer base, theme;

    @layer base {
        h1 {
            color: red;
        }
    }

    @layer theme {
        h1 {
            color: blue;
        }
    }

    @layer base {
        h1 {
            color: orange;
        }
    }
}

不过我们还可以使用下面这种方式达到上面代码等同的效果:

@layer framework {
    @layer base, theme;

    @layer base {
        h1 {
            color: red;
        }
    }

    @layer theme {
        h1 {
            color: blue;
        }
    }
}

/* 样式将被添加到`framework`级联层中的`base`级联层 */
@layer framework.base {
    h1 {
        color: orange;
    }
}

也就是说,如果你要在外层引用某个级联层嵌套的级联层时,可以用它的全名(父级联层名称+嵌套的级联层,只不过他们之间用点号.来连接),比如myLayer.mySubLayer,当然前面可以使用 @layer 或带有layer关键词或layer()函数的@import,比如:

@layer myLayer.mySubLayer {};

@import url(mysublayer.css) layer(myLayer.mySubLayer);

@import url(mysublayer.css) myLayer.mySubLayer;

@import(mysublayer.css) layer(myLayer.mySubLayer);

最后再看一个示例。嵌套一个匿名的级联层,比如:

@layer base {
    h1 {
        color: orange;
    }
}

@layer framework {
    @layer base, theme;

    @layer base {
        h1 {
            color: lime;
        }
    }

    @layer {
        h1 {
            color: #09f;
        }
    }

    @layer theme {
        h1 {
            color: yellow;
        }
    }
}

上面示例,在frameworkd 级联层中使用 @layer规则创建了一个匿名的级联层(@layer后紧跟{},没有级联层名称)。这样的操作同样是有效的,可以看到效果的,也就是说,同样可以在级联层中嵌套匿名级联层,只不过后面无法在别的地方给他附加样式。

有关于级联层嵌套就和大家聊到这里,预留一个问题:CSS级联算法对匿名级联层的优先级(权重)是如何计算的? 我们把这个问题留下面的小节中来回答。

匿名级联层优先级如何计算

在介绍级联层顺序时,我们已知道结论:

位于最后的级联层优先级最高,如果有隐含级联层存在,那它的优先权高于所有声明的级联层

在上一节结尾处留一个问题,匿名级联层权重如何计算。在回答该问题或者得出相关结论之前,我们先来看一个带有匿名级联层的示例:

<!-- HTML -->
<h1 class="title" id="title">Layer Nesting</h1>

@layer base {
    h1 {
        color: orange;
    }
}

/* 匿名级联层 */
@layer {
    h1 {
        color: lime;
    }
}

@layer theme {
    h1 {
        color: yellow;
    }
}

就该示例而言,级联层的顺序将会按照 @layer 在源码代码出现顺序来评估级联层优先级:

  • ①:base
  • ②:Anonymous Layer (匿名级联层)
  • ③:theme

theme级联层位于所有已定义的级联层最后,他优先级最高,所以你看到的 h1 文本颜色是yellow

假设我们在上面示例基础上新增一个匿名级联层:

@layer base {
    h1 {
        color: orange;
    }
}

@layer {
    h1 {
        color: lime;
    }
}

@layer theme {
    h1 {
        color: yellow;
    }
}

@layer {
    h1 {
        color: #f36;
    }
}

相应的级联层顺序是:

  • ①:base
  • ②:Anonymous Layer (匿名级联层)
  • ③:theme
  • ④:Anonymous Layer (匿名级联层)

最后附加的匿名级联层成了最后一个级联层,因此它的权重最高,最终h1文本的颜色是 #f36。从这两个示例的结果来看,我们可以得到第一个结论:

使用 @layer 代码块(即 @layer layername {}@layer {})创建的级联层(不管是具名还是匿名的级联层),CSS级联在评估级联层的权重时遵循源码顺序标准,即最后一个@layer 创建的级联层优先级最高

我们在这个示例基础上加上隐含级联层:

h1 {
    color: #09f;
}

@layer base {
    h1 {
        color: orange;
    }
}

@layer {
    h1 {
        color: lime;
    }
}

@layer theme {
    h1 {
        color: yellow;
    }
}

@layer {
    h1 {
        color: #f36;
    }
}

这个情况之下,h1文本的颜色是#09f隐含级联层优先级是最高的,该原则依旧成立:

  • ①:base
  • ②:Anonymous Layer (匿名级联层)
  • ③:theme
  • ④:Anonymous Layer (匿名级联层)
  • ⑤:Unlayer(隐含级联层)

除了上面的方式可以创建级联层之外,还可以使用 @layer 单行语法规则对级联层顺序做预设,比如:

@layer base, theme;

@layer base {
    h1 {
        color: orange;
    }
}

@layer theme {
    h1 {
        color: lime;
    }
}

这个示例,级联层的顺序是:

  • ①:base
  • ②:theme

即,theme级联层优先级最高,h1文本的颜色是lime。如果我们在 basetheme 之间使用 @layer {} 插入一个匿名级联层:

@layer base, theme;

@layer base {
    h1 {
        color: orange;
    }
}

@layer {
    h1 {
        color: yellow;
    }
}

@layer theme {
    h1 {
        color: lime;
    }
}

前面说过了,@layer base, theme; 语句对已命名的级联层的顺序做了预设,在这种环境下,新创建的匿名级联层将会追加在已预设级联层的末尾。此时,级联层的顺序则变成:

  • ①:base
  • ②:theme
  • ③:Anonymous Layer (匿名级联层)

也就是说,匿名级联层优先级最高,h1的文本颜色则是yellow。再换一个情景,如果@layer 创建的匿名级联层位于 @layer 单行语法规则预设的级联层前面呢?

@layer {
    h1 {
        color: yellow;
    }
}

@layer base, theme;

@layer base {
    h1 {
        color: orange;
    }
}


@layer theme {
    h1 {
        color: lime;
    }
}

你可能已经想到了,此时级联层的顺序变成了:

  • ①:Anonymous Layer (匿名级联层)
  • ②:base
  • ③:theme

这种场景之下,匿名级联层排在了最前面,优先级最低,而theme级联层排在了最后,其优先级成了最高。h1的文本颜色则是 lime。这个时候,即使你在后成继续使用 @layer 创建匿名级联层也不会改变级联层顺序,因为新增的匿名级联层会合到最开始创建的匿名级联层:

@layer {
    h1 {
        color: yellow;
    }
}

@layer base, theme;

@layer base {
    h1 {
        color: orange;
    }
}


@layer theme {
    h1 {
        color: lime;
    }
}

@layer {
    h1 {
        color: #f36;
    }
}

级联层顺序没有改变:

  • ①:Anonymous Layer (匿名级联层)
  • ②:base
  • ③:theme
  • ④:Anonymous Layer (匿名级联层)

h1 的文本颜色来自于后面新增的匿名级联层的#f36。要是在这个基础有一个隐含级联层:

/* 隐含级联层 */
h1 {
    color: #09f;
}

/* 首个匿名级联层 */
@layer {
    h1 {
        color: yellow;
    }
}

/* 预设级联层顺序 */
@layer base, theme;

/* 给base级联层添加样式 */
@layer base {
    h1 {
        color: orange;
    }
}

/* 给 theme 级联层添加样式 */
@layer theme {
    h1 {
        color: lime;
    }
}

/* 新增匿名层,将会合进首个已创建的匿名级联层 */
@layer {
    h1 {
        color: #f36;
    }
}

这个示例的级联层顺序是:

  • ①:Anonymous Layer (匿名级联层)
  • ②:base
  • ③:theme
  • ④:Anonymous Layer (匿名级联层)
  • ⑤:Unlayer(隐含级联层)

h1的文本颜色来自于隐含级联层,即 #09f

这样我们可以得到第二个结论:

如果@layer {}创建的匿名级联层出现在 @layer单行语法预设级联层之前,则该匿名级联层会放置在已预设的级联层最前面,匿名级联层优先级最低;如果@layer{}创建的匿名级联层出现在@layer单行语法预设的级联层之后,则该匿名级联层会追加到已预设的级联层末尾(最后面),匿名级联层优先级最高;如果@layer{}同时出现在@layer单行语法预设的级联层前后,则会按照前面两种情况对匿名级联层排序,最终排在后面的匿名级联层获胜。对于隐含级联层而言,它始终会在所有级联层最后,具有最高的优先级

上面展示的示例都是平级的,没有嵌套的级联层。其实上面的两条结论同样适用于嵌套的级联层。比如下面这个示例:

@layer base {
    h1 {
        color: orange;
    }
}

@layer framework {
    @layer base, theme;

    @layer base {
        h1 {
            color: lime;
        }
    }

    @layer {
        h1 {
            color: #09f;
        }
    }

    @layer theme {
        h1 {
            color: yellow;
        }
    }
}

@layer {
    h1 {
        color: red;
    }
}

根据前面介绍的,示例中的所有级联层的顺序则是:

  • ①:base
  • ②:framework.base
  • ③:framework.theme
  • ④:framework级联层内部的匿名级联层,你可以看作是 framework.anonymouslayer
  • ⑤:Anonymous Layer (匿名级联层)

从上面的排序不难发现,排在最外面的匿名级联层优先级权重最高,因此 h1 文本颜色是red

注意,外部的匿名级联层(@layer)和嵌套在framework级联层的匿名级联层,并不是相同的匿名级联层!

我们再调整一下上面的示例,让它看上去更复杂一点点:

@layer {
    h1 {
        color: red;
    }
}

@layer base {
    h1 {
        color: orange;
    }
}

@layer framework {
    @layer {
        h1 {
            color: #2290ef;
        }
    }

    @layer base, theme;

    @layer base {
        h1 {
        color: lime;
        }
    }

    @layer {
        h1 {
            color: #09f;
        }
    }

    @layer theme {
        h1 {
            color: yellow;
        }
    }

    @layer {
        color: #909fff;
    }
}

@layer {
    h1 {
        color: #900aef;
    }
}

千万别被上面示例代码吥倒了,事实上没有你想的那么复杂。我们只需要按照前面的原则,把所有级联层按下面的方式,将其列出来,就可以很快知道最终哪个级联层优先级最高:

  • ①:Anonymous Layer (匿名级联层)
  • ②:base
  • ③:framework级联层内部的匿名级联层,你可以看作是 framework.anonymouslayer
  • ④:framework.base
  • ⑤:framework.theme
  • ⑥:framework级联层内部的匿名级联层,你可以看作是 framework.anonymouslayer
  • ⑦:Anonymous Layer (匿名级联层)

最后出现的匿名级联层获胜,h1文本颜色为#900aef

是不是没有你想象的那么复杂,但实际使用的时候,还是建议不要这么使用,而且应该尽可能的避免匿名级联层的使用。现在我们可以来回答标题所提的问题了:

CSS级联对于匿名级联层的权重的评估始终遵循的是级联层在源码中的顺序,简而言之,出现在最后的级联层优先级最高,不管是具名(显式命名的级联层)还是匿名级联层,都是如此。除隐含级联层之外,因为隐含级联层始终是在所有级联层(包括具名和匿名级联层)最后

级联层和 !important 的使用

CSS 级联在评估来源(Origin)标准时,CSS 级联对几个来源的排序如下(从高到低排序):

你可能已经注意到带有!important字样的来源与正常(即不带!important字样的来源)的对应物的顺序正好相反,这其实就是 CSS 的工作方式!

当一个声明被显式标记为 !important 时,它在级联中的权重就会增加并颠倒优先顺序

这个反转规则也适用于级联层中的声明:带有 !important 声明将被放在“重要开发者”来源(Important Author Origin),但与“正常作者”来源(Normal Author Origin)相比,级联层将有相反的顺序

在级联层中,当比较属于不同级联层的声明时,那么对于正常的规则,级联层在最后的声明获胜,而对于重要的规则(带有!important的规则),级联层在前的声明获胜。

来看一个示例,使用 @layer 声明了四人级联层:

@layer reset, base, theme, utilities;

这些级联层中的正常声明都在“正常作者”来源(Normal Author Origin)中,并将按此排序:

  • ①:正常 reset 级联层
  • ②:正常 base 级联层
  • ③:正常 theme 级联层
  • ④:正常 utilities 级联层

然而,这些级联层中的重要声明(带有!important声明)都将进入“重要用户”来源(Important User Origin),并将被反向排序:

  • ①:重要 utilities 级联层
  • ②:重要 theme 级联层
  • ③:重要 base 级联层
  • ④:重要 reset 级联层

因为“普通未分层样式”(普通隐含级联层,Normal Unlayered Styles)隐含在最后,这也意味着“重要未分层样式”(重要隐含级联层,Important Unlayered Styles)将被放置在第一位。所以,在级联层中的“重要声明”会赢过在隐含级联层中的“重要声明”。这个重要声明指的是带有 !important 标记的声明。

<!-- HTML -->
<h1 class="title">With !important</h1>

<p class="title">Without !important</p>

/* CSS */
@layer base, theme, component;

@layer base {
    h1 {
        color: orange !important;
    }

    p {
        color: orange;
    }
}

@layer theme {
    h1 {
        color: lime !important;
    }

    p {
        color: lime;
    }
}

@layer component {
    h1 {
        color: yellow !important;
    }

    p {
        color: yellow;
    }
}

这个示例中,使用 @layer 声明了 basethemecomponent 三个级联层,在相应的级联层中都给 h1p 元素设置了 color 样式规则,不同的是,h1color值带有 !important 标识符。

先来看不带!importantp元素,相应的级联层顺序:

  • ①:base
  • ②:theme
  • ③:component

最终级联层component获胜,因此p元素文本的颜色是yellow。而带有!importanth1元素对应的级联层却刚好相反:

  • ①:component
  • ②:theme
  • ③:base

最终级联层 base 获胜,因此h1元素文本的颜色是orange

如果我们在同一个元素不同级联层中声明样式规则时,有的带有!important声明,有的则没有:

@layer reset, base, theme, component;

@layer reset {
    h1 {
        color: #f36;
    }
}

@layer base {
    h1 {
        color: orange !important;
    }
}

@layer theme {
    h1 {
        color: lime !important;
    }
}

@layer component {
    h1 {
        color: yellow;
    }
}

resetcomponent级联层样式规则没有带!importantbasetheme级联层中样式规则带有!important。虽然 @layer 规则预设了级联层顺序:

  • ①:reset
  • ②:base
  • ③:theme
  • ④:component

但带有!important标识符时,级联层顺序则变成了:

  • ①:reset
  • ②:component
  • ③:theme
  • ④:base

最终运用于h1元素的color值是来自于base级联层,即orange

是不是有点晕,感觉是玄学一样。如果不看代码在浏览器中的渲染效果,真的整不明白,最终CSS级联算法会评估哪个级联层,哪条规则获胜。幸运的是,我们一直提倡在编写CSS代码的时候,不需要使用 !important。需要特别提出的是,@layer规则后不能带有!important标识符:

级联层和条件CSS规则的相互嵌套

从《图解CSS:条件 CSS》一文中可以知道 CSS 的 @media@supports 可以根据提供的条件来设置不同的样式规则。 在CSS中,@layer 和这些条件CSS的@规则可以相互嵌套,但所起的作用则有所差异。我们先来看 @layer@media的嵌套。

CSS媒体查询 @media 可以像其他CSS样式规则一样直接放在 @layer代码块里面,比如:

@layer base {
    h1 {
        color: red;
    }

    @media screen and (max-width: 760px) {
        h1 {
            color: orange;
        }
    }
}

这个示例中的 @media 只被用于 base 级联层中。当媒体条件为真(true)时,base 级联层中的 h1colororange,反之则会取 colorred

也就是说,如果在base级联层之外还有其他样式规则,比如其他级联层,而且优先级还高于base级联层时,那么位于base级联层里的媒体查询会看上去无效。比如在上面示例基础新增一个theme级联层:

@layer base {
    h1 {
        color: red;
    }

    @media screen and (max-width: 760px) {
        h1 {
            color: orange;
        }
    }
}

@layer theme {
    h1 {
        color: lime;
    }
}

在这个示例中,即使媒体查询条件为真,base级联层运用了媒体查询中的样式规则(h1{color:orange}),但theme级联层优先级高于base级联层。因此,不管视窗宽度如何改变,最终 h1 都取自theme级联层中的样式规则,即colorlime

除了将媒体查询放在级联层里这种使用方式之外,还可以将级联层放在媒体查询里面。比如:

@media screen and (max-width: 760px) {
    @layer base {
        h1 {
            color: orange;
        }
    }
}

当一个@layer被嵌套在一个@media中时,只有媒体查询条件为真时,级联层才会被使用。比如下面这个示例:

@layer reset {
    h1 {
        color: yellow;
    }
}

@media screen and (min-width: 760px) {
    @layer base {
        h1 {
            color: orange;
        }
    }
}

@media (prefers-color-scheme: dark) {
    @layer theme {
        h1 {
            color: lime;
        }
    }
}

该示例中级联层的使用和媒体查询条件真假有着紧密的关系

  • 如果两个媒体查询条件都为假(false)时,只有reset级联层中的样式规则被运用,此时,h1的文本颜色为yellow
  • 如果第一个媒体查询条件成立(true),第二个媒体查询条件不成立(false),这个时候级联层相当于 @layer reset, base;base级联层优于reset级联层,此时,h1 的文本颜色为 orange
  • 如果第一个媒体查询条件不成立(false),第二个媒体查询条件成立(true),这个时候级联层相当于 @layer reset, theme;theme级联层优于reset级联层,此时,h1的文本颜色为 lime
  • 如果两个媒体查询条件都成立(true),这个时候级联层相当于 @layer reset, base, theme;theme级联层优先级最高,此时,h1的文本颜色为 lime

如果想避免这种行为的话,建议事先使用 @layer 对级联层顺序做预设,并且应该尽可能避免在媒体查询规则中定义新的级联层。

在 CSS 中,@supports@media 类似,可以根据相应的条件结果返回相应的结果。同样的,@supports 也以和 @layer 相互嵌套:

/* base 级联层始终生效*/
@layer base {

    /* 支持 display: grid 的浏览器生效 */
    @supports (display: grid) {
        .container {
            display: grid;
        }
    }
}

/* 只有支持grid 的浏览器,grid级联层才会被创建 */
@supports (display: grid) {
    @layer grid {
        .container {
            display: grid;
        }
    }
}

级联层和定义名称的规则

CSS中有很多带有 @ 前缀的规则,比如上面介绍的 @media@supports 有条件判断的能力。除此之外,还有一些 @ 规则称为“定义名称的规则”,比如我们熟悉的 @keyframes@font-face,以及新出的@scroll-timeline规则。但它们和 @layer 规则的嵌套和@media有所不同。就拿 @keyframes 来说吧,它能放在@layer中,但@layer一般不嵌套在@keyframes中。

@layer framework, override;

@layer framework {
    @keyframes slide-left {
        from {
            margin-left: 0;
        }
        to {
            margin-left: -100%;
        }
    }
}

@layer override {
    @keyframes slide-left {
        from {
            translate: 0
        }
        to {
            translate: -100% 0;
        }
    }
}

.sidebar {
    animation: slide-left 300ms linear;
}

在代码的第一行使用 @layerframeworkoverride 级联层的顺序做了预设,即 override 级联层优先级高于 framework,因此,用于.sidebaranimation-name 是来自于 override 级联层的slide-left,使用的是translate

注意,tanslateCSS Transform 中的下一代语法规则,详细请参阅《下一代CSS的Transform》一文。

不允许@import@namespace@layer交错使用

在 Github 中有关于级联的讨论中,《Proposal to disallow interleaving of @layer and @import rules》这个Issues解释了为什么不允许@import(或@namespace)和@layer不能一起交错使用的原因(这个Issue也跟踪了该话题的一些讨论)。

从 CSS 解析器看到 @layer 跟在一个较早的 @import 后面的那一刻起,它之后的所有 @import 规则都将被忽略。

@layer default;

@import url(theme.css) layer(theme);

@layer components; /* 这个@layer 跟在上面的@import之后 */

@import url(default.css) layer(default); /* 这个 @import 以及后面所有 @import 规则被忽略 */

@layer default {
    audio[controls] {
        display: block;
    }
}

为了解决这个问题,可以将你的@import规则分组:

@layer default;

@import url(theme.css) layer(theme);
@import url(default.css) layer(default);

@layer components;

@layer default {
    audio[controls] {
        display: block;
    }
}

在《关键渲染路径(CRP)》和《消除阻塞页面渲染的资源》两篇文章中多次提到过,为了提高 Web 页面的渲染性能,我们应该尽可能的避免使用@import引入CSS文件。如果你实在要使用@import规则,特别是要和@layer交织使用的话,应该按下面的方式来使用:

  • 使用 @layer 单行语法规则对级联层的顺序做预设
  • 将所有 @import 语句放在第一个 @layer 语句之后
  • 使用 @layer 块语法规则,将CSS规则附加到已经建立的级联层上

比如:

@layer default, theme, components;

@import url(theme.css) layer(theme);
@import url(default.css) layer(default);

@layer default {
    audio[controls] {
        display: block;
    }
}

小结

随着 CSS 级联层的到来,Web开发者有了更多控制CSS级联的能力。CSS级联层的真正威力来自于它在级联中的独特位置:在选择器的权重和源码顺序之前。正因为如此,我们不需要担心在其他层中的 CSS 的选择器权重,也不需要担心我们将CSS加载到这些层中的顺序。这对于开发一个大型项目,或者在一个大型团队多人协作或加载第三方CSS时非常有利。

CSS级联层将来可能还会改变我们编码的方式,并给我们带来新的功能,使项目的封装变得非常容易。最终,像级联层这样的新功能将推动一整套新的后期处理和前端Web技术,这将改变我们今后的Web网站建设方式。

如果你阅读到这里,也说明你对CSS级联层非常感兴趣,你也可能已经体验了CSS级联层给CSS带来的威力以及给开发者带来的便利。即使到现在,该特性在主流浏览器中还只是实验性特性,但对于 Web 开发者而言总是好事。在未来可能就不仅是实验性特性,将会是CSS的真正能力。也就是说,我们又有了一种新的能力来控制CSS。当然,如果要快速推进该特性,还需要大家一起努力。如果你在体验CSS级联层时,碰到不正常的现象,可以分别给相应的浏览器团队提Issue(Chromium: Issue #1095765Firefox: Issue #1699215Safari: Issue #220779)。

如果你希望更深入的了解CSS级联层的能力,除了阅读该文,还可以阅读下面这些资源:

返回顶部