您的浏览器即将推出级联层

尤娜·克拉维兹
Una Kravets

Chromium 99、Firefox 97 和 Safari 15.4 Beta 版即将推出级联层(@layer CSS 规则)。使用样式可以更明确地控制 CSS 文件,以防止样式特异性冲突。这对于大型代码库、设计系统以及在应用中管理第三方样式的情况尤其有用。

明确地对 CSS 进行分层可防止出现意想不到的样式替换,并有助于优化 CSS 架构。

CSS 特异性和级联

CSS 特异性是指 CSS 如何确定要应用于哪些元素的样式。您可以使用的不同选择器决定任何样式规则的特异性。例如,元素没有类或属性具体,而类或属性又不如 ID 具体。这是 CSS 学习的基本组成部分。

人们会使用 BEM 等 CSS 命名惯例来防止无意中覆盖特异性。通过为所有内容指定一个类名,所有内容都放置在相同的特异性平面上。不过,并非总是能保持这种条理清晰的样式,特别是在采用第三方代码和设计系统时。

包含课程的卡片的 BEM 可视化内容
来自 keepinguptodate.com 的 BEM 命名示例。

级联层旨在解决此问题。它们为 CSS 级联引入了一个新的。使用分层样式时,图层的优先级始终高于选择器的特异性。

例如,选择器 .post a.link 的特异性高于 .card a。如果您尝试为链接设置样式,那么在帖子中,您会发现应用更具体的选择器。

通过使用 @layer,您可以更明确地说明每种样式的样式特异性,并确保卡片链接的样式会替换帖子链接的样式,即使从数值上来看,如果所有 CSS 都位于同一平面上,这种特异性可能会较低。这是因为级联优先。分层样式可创建新的级联“平面”。

分解界面的项目演示图示

@layer 的实际应用

演示:使用导入功能显示链接颜色
请参阅 Codepen 演示

此示例展示了使用 @layer 的级联层的强大功能。系统会显示多个链接:一些链接未应用任何额外的类名称,一个链接使用了 .link 类,另一个链接使用了 .pink 类。然后,CSS 会添加三个层:basetypographyutilities,如下所示:

@layer base {
  a {
    font-weight: 800;
    color: red; /* ignored */
  }

  .link {
    color: blue; /* ignored */
  }
}

@layer typography {
  a {
    color: green; /* styles *all* links */
  }
}

@layer utilities {
  .pink {
    color: hotpink;  /* styles *all* .pink's */
  }
}

最终,所有链接都显示为绿色或粉色。这是因为:虽然.link选择器级别特异性高于a,但a有一种颜色样式的优先级更高@layer。当绿色规则位于蓝色规则之后的图层中时,a { color: green } 会替换 .link { color: blue }

层优先级高于元素特异性

组织图层

如上所示,您可以直接在页面上组织图层,也可以在文件的顶部组织图层。

层顺序是各层名称首次出现在您的代码中时确定的。

也就是说,如果您在文件顶部添加以下内容,链接全部会显示为红色,而包含类 .link 的链接会显示为蓝色:

@layer utilities, typography, base;

这是因为层顺序现在颠倒了,将实用程序放在最前面,将实用程序放在最后。因此,base 层中的样式规则的特异性始终高于排版层中的样式规则。这些链接将不再是绿色的链接,而是红色或蓝色的链接。

Codepen 项目的屏幕截图
请参阅 Codepen 演示

整理导入

使用 @layer 的另一种方法是使用导入文件。您可以在导入样式时使用 layer() 函数直接执行此操作,如以下示例所示:

/* Base */
@import '../styles/base/normalize.css' layer(base); /* normalize or rest file */
@import '../styles/base/base.css' layer(base); /* body and base styles */
@import '../styles/base/theme.css' layer(theme); /* theme variables */
@import '../styles/base/typography.css' layer(theme); /* theme typography */
@import '../styles/base/utilities.css' layer(utilities); /* base utilities */

/* Layouts */
@import '../styles/components/post.css' layer(layouts); /* post layout */

/* Components */
@import '../styles/components/cards.css' layer(components); /* imports card */
@import '../styles/components/footer.css' layer(components); /* footer component */

上述代码段包含三层:baselayoutscomponentsbase 中的标准化文件、主题文件和排版文件,layouts 中的 post 文件,components 中的 cardsfooter 文件。导入文件时,图层将使用图层函数进行实例化。另一种方法是在文件顶部组织层,并在执行任何导入前声明这些层:

@layer base,
       theme,
       layouts,
       components,
       utilities;

现在,@import 样式的顺序对图层顺序无关紧要,因为图层名称已在第一个实例中建立。这样您就少了一件担心的问题。您仍然可以将导入的文件设置为特定图层,但顺序已经确定。

来自 Codepen Project 的屏幕截图
探索 Codepen 上的项目。

层和级联

我们回过头来看看层在什么地方使用,因为它与更广泛的级联相关:

级联图示

优先顺序如下:

  • 常规用户代理(优先级最低)
  • 本地用户 @layer
  • 本地用户常规
  • 作者:@layers
  • 作者正常
  • 作者 !important
  • 作者 @layer !important
  • 本地用户 !important
  • 用户代理 !important**(最高优先级)

您可能会注意到,此处 @layer !important 样式反转了。与非层(普通)样式相比,这种样式没有那么具体,优先级更高。这是因为 !important 在级联中的工作原理:它会打破样式表中的正常级联,并反转正常的层级特异性(优先级)。

嵌套层

图层也可以嵌套在其他图层中。以下示例来自 Miriam Suzanne 的级联层说明

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

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

  p { margin-bottom: 1em; }
}

在上面的代码段中,您可以访问 framework.default,方法是使用 . 作为嵌套在 framework 中的 default 层的记号。您还可以采用更简写的格式编写代码:

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

生成的层和层顺序如下:

  • 默认
  • framework.default
  • framework 未叠加
  • 未分层的

注意事项

如果使用正确,级联层确实很实用,但也可能会产生额外的困惑和意想不到的结果。使用级联层时,请注意以下几点:

规则 1:不使用 @layer 限定范围

级联层无法解决作用域问题。如果您有一个包含 @layer 的 CSS 文件(例如 card.css),并且想要设置卡片内所有链接的样式,请不要编写如下样式:

a {
  …
}

这样一来,您文件中的所有 a 标记都会获得此替换值。正确限定样式仍很重要:

.card a {
  …
}

规则 2:级联层在非层 CSS 之后排序

请务必注意,分层 CSS 文件不会替换非分层 CSS。这是一项刻意的决定,可让您更轻松地以更合理的方式引入层,以便与现有代码库配合使用。例如,使用 reset.css 文件就是很好的起点和级联用例用例。

规则 3:!important 反转级联特异性

虽然分层样式通常不如未分层样式那么具体,但使用 !important 可以反转这一点。在层中,使用 !important 规则的声明比无层样式更具体。

在这种情况下,!important 样式会与其特异性相反。上图显示了这种情况,仅供参考:author @layers 的优先级低于 author @layers 优先级,其优先级低于 author !important 优先级,且优先级低于 author @layer !important。

如果您有多个图层,具有 !important 的第一个图层会优先使用 !important,并且是最具体的样式。

规则 4:了解注入点

由于图层顺序是每个图层名称首次出现在代码中时确定的,因此如果您在导入并设置 layer() 之后放置 @layer 声明,或者在其他 @layer 语句之后放置该声明,则可以忽略该声明。与在 CSS 中不同,在 CSS 中,页面最靠下的样式规则应用于级联层,首先确定顺序。

可以以列表、图层块或导入的形式保存。如果您使用 layer()@layer 放在导入列表后面,则系统不会执行任何操作。将其放在文件顶部可设置层顺序,并有助于您清楚地看到架构中的层。

第 5 条规则:观察您的特异性

使用级联层时,如果不太具体的选择器位于更具体的层上,则不够具体的选择器(例如 a)会替换更具体的选择器(例如 .link)。请注意以下几点:

如果已指定 @layer utilities, components,则 layer(components) 中的 a 会替换 layer(utilities) 中的 .pink。虽然这是 API 的特意功能,但如果不预期,您可能会感到困惑和沮丧。

因此,如果您要编写实用程序类,请始终将其作为级别高于要替换它们的组件包含的层。您可能会想,“我刚刚添加了这个 .pink 类来更改颜色,但系统未应用颜色”。

详细了解级联层

您还可以查看以下资源以详细了解级联层: