likes
comments
collection
share

未来如何更好的避免样式冲突?CSS @layer

作者站长头像
站长
· 阅读数 31

@layer 是什么?

我们来看MDN上的定义:

The @layer CSS at-rule is used to declare a cascade layer and can also be used to define the order of precedence in case of multiple cascade layers.

翻译一下:at-rules 中的 @layer 用于声明级联层(cascade layer),同时可用于定义多个级联层的优先级

at-rules 是什么?

At-rules是指导 CSS 如何表现的CSS 语句。它们以 at 符号 ' @' ( U+0040 COMMERCIAL AT) 开头,后跟一个标识符,包括直到下一个分号 ' ;' ( U+003B SEMICOLON) 或下一个CSS 块的所有内容。

at-rules 有哪些?

如图,大家应该挺眼熟,这里就不一一介绍了

未来如何更好的避免样式冲突?CSS @layer

级联层(Cascade Layers)是什么?

上面我们提到了@layer用于声明级联层,那级联层是什么?

我们参看CSS Cascading and Inheritance Level 5(13 January 2022)中6.4节

未来如何更好的避免样式冲突?CSS @layer

和级联起源一样在用户和开发者样式之间提供了一种平衡,级联层提供了一种结构化的方式来组织和平衡单个来源中的问题。单个级联层内的规则级联在一起,不与层外的样式规则交错。

开发者可以创建层来表现元素默认值、第三方库、主题、组件、覆盖和其他样式问题,并且能够以显式方式重新排序层级,而无需更改每个层内的选择器或特异性,或依赖源顺序来解决跨层的冲突。

级联层为了解决什么问题?

单纯看官方定义可能比较晦涩,我们来假设日常开发中的一个场景,从场景去理解级联层解决什么问题

未来如何更好的避免样式冲突?CSS @layer

假设如下样式在第三方组件库中

    /* 第三方库 */
    #app .item {
      color: green;
      border: 5px solid green;
      font-size: 1.3em;
      padding: 0.5em;
      width: 120px;
    }

我们想要将'display'文案的颜色由绿色改成红色就需要借助级联中增加特异性(优先级)的手段在开发页面中添加如下:


    /* 开发者样式 */
    #app .item>span {
      color: red;
    }

或者借助级联中出场顺序对优先级的影响在用户页面中重写


    /* 开发者样式 */
    #app .item {
      color: red;
    }

效果如下

未来如何更好的避免样式冲突?CSS @layer

再举个例子:

比如有可能第三方组件写了

a { color: blue; }

那项目开发中由于引入第三方组件 往往都在项目设置的通用样式比如common.css后加载,就会导致样式污染。

如果想稍后在代码中覆盖某些属性,使用高特异性选择器的语句可能会导致问题。然后因为有问题就选择更高特异性的择器的语句或使用!important,这不仅仅会使代码冗长也可能会带来副作用。

低特异性选择器的语句很容易被稍后出现在代码中的语句覆盖。在您自己的代码之后加载第三方 CSS 时,这尤其麻烦。

级联层的出现就是为了解决类似问题使 CSS 开发者可以更简单直接地控制级联。

刚说了很多级联层的概念,为了更好地理解级联层的作用以及理解一些现象背后的根因我们需要补充以下一些关于级联的基础知识。

如何理解级联(cascade)

级联

CSS中有两个重要的基础规则,一个是级联,一个是继承。

继承指的是类似 color, font-family 等属性父元素设置后,子元素会继承的特性。

级联 可以简单理解为是CSS 用来解决要应用于元素的具体样式的算法。也就是基于一些优先级排序输出给给定元素上属性值一个级联值。级联值是级联的结果。

当前级联的排序标准是什么

我们参看CSS Cascading and Inheritance Level 5(13 January 2022)中6.1节,如图:

未来如何更好的避免样式冲突?CSS @layer

相比于CSS Cascading and Inheritance Level 4(14 January 2016)中的定义比较后发现变化明显。

最重要的变化就是增加了级联层。目前的顺序是:

  1. 起源和重要性
  2. 语境
  3. 样式属性
  4. 图层(Layers)
  5. 特异性
  6. 出场顺序(又名源代码顺序)

浏览器在确定最终元素样式呈现的时候,会依据这些准则按照优先权从高到低排序,并且会一个一个的检查,直到确定最终样式。

层在级联中的什么位置?

有两张图直观形象的帮助我们理解:

未来如何更好的避免样式冲突?CSS @layer

未来如何更好的避免样式冲突?CSS @layer

我们会发现选择器特异性(selector specificity)只是级联中的一小部分但也是我们平时操作最多的一部分。也理解为什么内联样式优先级高。于此同时我们惊讶的发现!important在级联中有特殊地位。

级联起源(Cascading Origins)和重要声明:!important

三个核心起源

css中每个样式规则有三个核心起源,它决定了它进入级联的位置,理解起源优先级是理解级联的关键。:

  • 浏览器(用户代理来源)
  • 浏览器设置 (用户来源)
  • Web作者(作者来源)

默认情况下:作者样式表(author style sheet )中的规则>用户样式表(user style sheet)>用户代理默认样式表user-agent style sheet),为了平衡这一点当样式声明标记为重要也就是添加!important之后 会使它们的优先顺序颠倒。

来源的优先级

声明的来源取决于它的来源,其重要性在于它是否用!important声明(见下文)。各种来源的优先级按降序排列:

  1. 过渡声明[css-transitions-1]
  2. 重要的 用户代理声明
  3. 重要的 用户声明
  4. 重要 作者声明
  5. 动画声明[css-animations-1]
  6. 普通 作者声明
  7. 普通 用户声明
  8. 普通 用户代理声明

过渡和动画我们这里可以暂时忽略。

重新认识 !important

我们经常将!important视为一种增加特异性的方法,以覆盖内联样式或高优先级的选择器。但是我们没理解!important设计出来的主要目的是作为整体级联中的一个特征,不仅仅简单的增加特异性更多的是带来样式间的平衡。

因此,!important为浏览器和用户提供了一种在最重要时重新声明其优先级的方法。

对于浏览器和用户来说,它是重新获得控制权的非常强大的工具。浏览器默认样式表包含我们开发者无法覆盖的重要样式。举个例子:

浏览器对具有'hidden'类型的input输入框设置了默认的展示属性并且将其声明为重要

input[type=hidden i] { display: none !important; }

未来如何更好的避免样式冲突?CSS @layer

未来如何更好的避免样式冲突?CSS @layer 从上面的浏览器表现我们可以看到我们web开发者在作者样式表中设置的规则在级联算法中最终落败于用户代理样式表中的相同规则。这验证了我们上面说的:在级联中!important(重要声明)会颠倒三大来源默认优先顺序。顺便吐槽下chrome控制台这边的样式显示有点bug没生效的规则不划删除线,生效的反而划删除线了。

再看一个官方文档的例子加强一下理解:

未来如何更好的避免样式冲突?CSS @layer

font-size的值最终是‘12pt ’因为作者样式表中标注重要声明的规则优先权高于用户样式表,text-indent的值虽然两个样式表都对其标注了重要,但是重要的用户声明优先级大于作者声明所以最终是‘1em’。

理解起源优先级是理解级联的关键。

使用@layer 探索

@layer 这个 CSS at-rule(AT规则)的句法如下:

@layer layer-name {rules}
@layer layer-name;
@layer layer-name, layer-name, layer-name;
@layer {rules}

级联层中的规则级联在一起,为 Web 开发人员提供了对级联的更多控制。不在一个层中的任何样式都聚集在一起并放置在一个匿名层中,该层位于所有声明的层之后,命名层和匿名层。这意味着在图层之外声明的任何样式都将覆盖在图层中声明的样式,而不管具体性如何。

创建级联层

可以通过多种方式创建级联层。

第一种方法是:创建命名的级联层,其中包含该层的 CSS 规则,如下所示:

@layer green {
  .item {
    color: green;
    border: 5px solid green;
    font-size: 1.3em;
    padding: 0.5em;
    width: 120px;
  }
}

@layer special {
  .item {
    color: rebeccapurple;
  }
}

第二种方法是:创建一个命名的级联层而不分配任何样式。这可以是单层,如下所示:

@layer green;

可以一次定义多个层,如下图:

@layer green, special

这很有用,因为声明层的初始顺序指示哪个层具有优先权。与声明一样,如果在多个层中找到声明,最后列出的层将获胜。

规则special将被应用,即使它的特异性低于 中的规则theme。这是因为一旦建立了层顺序,就会忽略出现的特殊性和顺序。这可以创建更简单的 CSS 选择器,因为您不必确保选择器具有足够高的特异性来覆盖竞争规则;您只需要确保它出现在后面的图层中。

**注意:**声明图层名称并设置它们的顺序后,您可以通过重新声明名称来将 CSS 规则添加到图层。然后将样式附加到图层,并且图层顺序不会更改。比如如下代码虽然layer green重新声明了并在文件后方但是由于顺序一开始已经设置所以字体颜色还是紫色:

@layer green,special;

@layer special {
  .item {
    color: rebeccapurple;
  }
}
    
@layer green {
  .item {
    color: green;
    border: 5px solid green;
    font-size: 1.3em;
    padding: 0.5em;
    width: 120px;
  }
}

未来如何更好的避免样式冲突?CSS @layer

第三种方法是:创建一个没有名称的级联层。例如:

@layer {
  .item {
    color: black;
  }
}

这将创建一个匿名级联层。该层的功能与命名层相同;但是,以后无法为其分配规则。匿名层的优先顺序是层被声明、命名或不命名的顺序,并且低于在层外声明的样式。

第四种是:使用@import在这种情况下,规则将在导入的样式表中。请记住,@import必须在除@charset之外的所有其他类型的at-rule规则之前

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

同时,也支持匿名引入,例如:

@import url(index.css) layer;

可能的第五种方式仍在讨论中:通过元素上的属性。请参阅CSSWG 问题 #5853

图层嵌套

图层可以嵌套。例如:

@layer base {
  p { max-width: 70ch; }
}

@layer framework {
  @layer base {
    p { margin-block: 0.75em; }
  }

  @layer theme {
    p { color: #222; }
  }
}

生成的层可以表示为一棵树:

  1. base
  2. framework
    1. base
    2. theme

或可以用扁平列表表示

  1. base
  2. framework.base
  3. framework.theme

要将样式附加到嵌套层,您需要使用以下全名来引用它:

@layer framework {
  @layer default {
     p { margin-block: 0.75em; }
  }

  @layer theme {
    p { color: #222; }
  }
}

@layer framework.theme {
  /* 这些样式会被添加到framework层里面的theme层 */
  blockquote { color: rebeccapurple; }
}

看效果:

未来如何更好的避免样式冲突?CSS @layer

未来如何更好的避免样式冲突?CSS @layer

图层排序

级联层按照它们声明的顺序排序。第一层优先级最低,最后一层优先级最高。但是,除此之外,未分层的样式具有最高优先级

@layer layer-1 { a { color: red; } } 
@layer layer-2 { a { color: orange; } } 
@layer layer-3 { a { color: yellow; } } 
/* 未分层 */a { color: green; }

优先级顺序如下:

  1. 未分层样式
  2. layer-3
  3. layer-2
  4. layer-1

未来如何更好的避免样式冲突?CSS @layer

加上重要声明:!important之后的顺序

@layer layer-1 { a { color: red !important; } }
@layer layer-2 { a { color: orange !important; } }
@layer layer-3 { a { color: yellow !important; } }
/* 未分层 */ a { color: green !important; }

任何加上重要声明的样式都以相反的顺序应用

优先级顺序如下:

  1. !important layer-1
  2. !important layer-2
  3. !important layer-3
  4. !important 未分层样式

未来如何更好的避免样式冲突?CSS @layer 这里我们看到对应规则在chrome浏览器的显示是正确的。但是在开发者控制台中的样式一栏规则显示有问题。

具有嵌套层的排序

@layer layer-1 { a { color: red; } }
@layer layer-2 { a { color: orange; } }
@layer layer-3 {
  @layer sub-layer-1 { a { color: yellow; } }
  @layer sub-layer-2 { a { color: green; } }
  /* 未嵌套 */ a { color: blue; }
}
/* 未分层 样式 */ a { color: indigo; }

优先级顺序如下:

  1. 未分层 样式
  2. layer-3
    1. layer-3 未嵌套
    2. layer-3 sub-layer-2
    3. layer-3 sub-layer-1
  3. layer-2
  4. layer-1

未来如何更好的避免样式冲突?CSS @layer

级联层与媒体查询(和其他条件)

例如,以下层顺序将取决于匹配的媒体条件:

例如:

@media (min-width: 600px) {
  @layer layout {
    .item { font-size: x-large; }
  }
}

@media (prefers-color-scheme: dark) {
  @layer theme {
    .item { color: red; }
  }
}
 

如果第一个媒体查询基于视口尺寸匹配,则layout层将按层顺序排在第一位。如果仅颜色方案首选项查询匹配,则theme将按图层顺序排在第一位。如果两者都匹配,那么图层顺序将为layout, theme。如果两个都不匹配则不定义层。下图是两者都匹配的场景

未来如何更好的避免样式冲突?CSS @layer

层可以在一个地方被定义图层(以建立图层顺序),然后我可以再任何地方添加样式

/* 定义在一个地方 */
@layer my-layer;
/* 其他样式*/
...
/* 在某个地方添加样式 */
@layer my-layer { a { color: red; } }

目前浏览器支持程度

未来如何更好的避免样式冲突?CSS @layer

目前所有现代浏览器均已经支持 @layer 规则。所有浏览器厂商都全力支持的特性未来一定比较实用。

番外

w3c现行的css标准发布流程

CSS 的标准化流程由 W3C Cascading Style Sheets Working Group (CSSWG)——W3C层叠样式列表小组以及独立CSS专家组成。W3C 本身并不制定标准,而是作为一个论坛式的平台,接收来自小组成员的提交,并通过会议来商讨制定标准,所有的提交以及讨论都是公开透明的,可以在 W3C 网站上看到会议的记录,可以简单分为4个大阶段

工作草案( WD )

候选人推荐( CR )

提议的建议( PR )

W3C 推荐( REC )

下图可以帮助理解:

未来如何更好的避免样式冲突?CSS @layer

W3C 通过状态码表示规范的成熟度。成熟度从低到高排序如下图

未来如何更好的避免样式冲突?CSS @layer

再看下图包含layer概念的标准讨论已经到达CR阶段。

未来如何更好的避免样式冲突?CSS @layer

W3C 鼓励从 CR 开始可以作为日常使用。

参考资料

CSS Cascading and Inheritance Level 5

A Complete Guide to CSS Cascade Layers

The Future of CSS: Cascade Layers (CSS @layer)

CSS必学基础:理解CSS中的级联规则 详解日后定会大规模使用的CSS @layer 规则

W3C Process Document

Cascade Layers Explainer