要将层叠层引入现有 CSS,首先要决定旧规则应放在哪里。普通的未分层规则优先级高于所有命名层,因此只有在将相互冲突的旧规则移入优先级较低的层后,新的分层覆盖规则才能生效。

本指南适用于在 Mac 上编辑已编写的网页,并在桌面浏览器中检查页面的用户。具体任务是将现有样式表放在新的组件样式之下,同时保留清晰易懂的迁移路径。示例使用本地文件、虚构标签和简单的 CSS 结构。

为什么新的分层样式会输给现有规则?

对于作者样式表中相互冲突的普通声明,未分层 CSS 的优先级高于任何显式层中的 CSS。选择器的特异性更高,或在层内的位置更靠后,都无法越过这一界限。CSS 规范通过一个未分层规则覆盖特异性更高的分层规则,演示了这一行为。CSS 层叠与继承第 5 级。

看下面这段 CSS,它的目标元素带有 tile 类:

.tile {
  background-color: #b8bdc5;
}

@layer components {
  .tile {
    background-color: #246a73;
  }
}

背景仍然是灰色。将层命名为 overrides、把代码块移到文件更靠后的位置,或提高其选择器的特异性,都不会改变普通未分层规则的优先级。层名称没有内置含义;它们的声明顺序决定了彼此之间的优先级关系。MDN:层叠层。

下表假设相互冲突的样式表声明属于相同的作者来源和上下文,且没有内联样式、动画或过渡影响该属性。每行从低到高读取优先级。

声明类型 优先级从低到高
普通声明 先声明的层 → 后声明的层 → 未分层规则
!important 未分层规则 → 后声明的层 → 先声明的层

层优先级先于选择器特异性进行判定。在优先级胜出的层内,特异性仍然重要;如果声明的特异性相同,则可以由源代码顺序决定胜出者。重要声明的顺序恰好相反,因此迁移时必须分别考虑普通规则和重要规则。MDN:层叠层。

应该选择什么迁移边界?

从一个新 CSS 需要控制的元素和属性开始。检查当前胜出的声明,找出它所在的样式表,并确定能否修改该文件或页面加载它的方式。这样就能找到最小且有效的迁移边界。

你的情况 实际操作 结果
现有 CSS 必须保留优先级 保持它未分层,并添加优先级较低的分层默认值 新层会在没有更强规则竞争时提供属性值
可以修改现有 CSS 将符合条件的旧规则放入 @layer legacy 后续的层可以覆盖普通旧规则
可以更改样式表入口文件 使用 layer(legacy) 导入现有文件 导入的样式表会加入该层,无需重写选择器
无法修改相互冲突的旧 CSS 或其加载方式 暂时将必要的覆盖规则保持为未分层 仍需处理特异性和其他层叠因素

这些选择对应 CSS 文档说明的三种层机制:层声明用于声明层名称,层代码块用于将规则分配到某一层,分层导入则用于将导入的样式表分配到某一层。仅声明层并不会移动现有规则。CSS 层叠与继承第 5 级:声明层叠层。

对于小型样式表,将普通规则整体包入层中可能比较方便。对于现有的外部文件,分层导入可以划定清晰的边界。不要将现有的 @import 规则放入 @layer 代码块中:导入属于顶层规则,其位置受到限制。MDN:@import。

下面推荐的迁移方式会将旧文件导入一个命名层。这会改变它相对于其他样式表的优先级,因此在将同样的改动应用到示例页面以外的页面前,请检查其他已加载的 CSS。

如何在 Mac 上创建完整的本地示例?

创建一个文件夹,放入以下三个纯文本文件。使用代码编辑器,并保留 .html 和 .css 扩展名。这些文件不需要任何软件包、脚本、字体或下载的资源。文件名仅用于本地示例。

将以下完整文档保存为 sample.html:

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Layer sample</title>
  <link rel="stylesheet" href="styles.css">
</head>
<body>
  <main id="sample">
    <h1>Shape sample</h1>
    <p>Tile A</p>
    <div class="tile" aria-label="Sample shape"></div>
  </main>
</body>
</html>

此文档只链接到 styles.css。HTML 使用 rel="stylesheet" 和 href 将外部样式表关联到页面。当前 HTML 标准没有为 link 元素定义 layer 属性;应通过 CSS 将资源分配到某一层。HTML 标准:link 元素。

将以下完整样式表保存为 legacy.css。它代表选择器保持不变的现有规则:

body {
  margin: 2rem;
  font-family: system-ui, sans-serif;
  color: #26313a;
  background-color: #f4f5f7;
}

#sample .tile {
  width: 6rem;
  height: 6rem;
  background-color: #b8bdc5;
  border-radius: 0;
}

将以下完整的入口样式表保存为 styles.css:

@layer legacy, components;
@import url("legacy.css") layer(legacy);

@layer components {
  .tile {
    background-color: #246a73;
    border-radius: 50%;
  }
}

在当前版本的桌面 Safari 或 Firefox 中打开 sample.html。预期结果是标签 Tile A 下方显示一个青绿色圆形。旧层提供尺寸,后声明的 components 层提供背景和圆角形状。由于两个声明在不同层之间竞争,单类选择器会胜过旧版 ID 选择器。MDN:@layer。

入口文件先确定层的顺序,将导入放在代码块之前,然后添加组件规则。这符合文档中规定的导入语法和位置要求。层声明可以放在导入之前;层代码块则不可以。MDN:@import。

现有页面应如何修改?

采用这种结构时,将页面直接引用的样式表替换为新的入口样式表。如果页面仍直接加载 legacy.css,同时又将它导入某个层,那么直接加载会提供未分层声明,从而可能破坏这次迁移。

可以通过预测示例中的故障来验证这一点:额外添加一个直接指向 legacy.css 的样式表链接,它的普通声明就可能让灰色方块重新出现。移除该直接加载后,旧规则便会留在预期的层中。这一预测符合规范规定的普通优先级顺序,即未分层规则位于显式层之后。CSS 层叠与继承模块第 5 级:层顺序。

在此示例中,将这三个文件放在一起。改用此方法时,请保留现有资源条件,例如媒体限制,并确认每个引用的样式表都已加载,然后再排查层叠冲突。

如何在 macOS 上使用 Firefox 验证迁移?

按 Command–Option–I 打开 Firefox 开发者工具箱,然后选择 Inspector。在文档树中选中示例形状,再查看 Rules 面板。Mozilla 文档说明了 Mac 上的工具箱快捷键,以及 Inspector 中的 CSS 检查控件。所有键盘快捷键、检查和编辑 CSS。

逐个属性检查示例:

  1. 找到 background-color。组件声明应胜过旧版的灰色值。
  2. 找到 border-radius。组件值应使形状变成圆形。
  3. 检查 width 和 height。它们的值仍应来自旧文件。
  4. 暂时禁用组件的背景声明。形状仍保持圆形,但旧版的灰色应会显现。

Firefox 会用删除线标示被覆盖的声明,并提供复选框以便暂时禁用它们。它还会将规则链接到源文件。使用这些控件找出相互冲突的声明,不要把每个意外结果都当成选择器问题。Mozilla:检查和编辑 CSS。

对于实际页面,请对已修改的各类状态重复检查:较窄的窗口、相关媒体条件以及组件变体。将需要保留的改动保存到源文件并重新加载页面;临时检查改动适用于诊断。

迁移过程中哪些情况可能出现意外变化?

重要声明会颠倒预期顺序

使用 @layer legacy, components 时,legacy 中的重要声明会胜过 components 中与之冲突的重要声明。因此,将旧的重要规则移入较早的层,可能会让它们更难覆盖。在相同作者来源和上下文中,所有分层样式表的重要声明也都优先于相互冲突的未分层样式表重要声明。MDN:@layer。

移动文件前,搜索其中的 !important,并检查每个相关冲突。如果两处声明都由你控制,请判断引入层之后是否仍需要重要声明。不要给每条新的组件规则都添加重要声明:这样会改变所使用的优先级机制。

内联样式需要单独处理。普通内联样式优先于普通作者样式表规则,重要内联样式则优先于其他作者重要声明。将样式表移入某一层,并不会移动元素的 style 属性。MDN:CSS 层叠简介。

后续声明无法重新排列已有层

层的顺序由其首次声明时确定。重新打开一个命名层只会向其中添加规则,不会移动它。在页面首次创建这些层之前(包括通过导入的样式表创建层),就应声明预期顺序。CSS 层叠与继承模块第 5 级。

渐进迁移时,保持初始顺序简短明确。先移动一组完整的相关规则,检查胜出的声明,再继续迁移。只移动一条规则,却将相互冲突的未分层副本留在原处,通常会保留你原本想消除的优先级界限。

在 Mac 上有哪些浏览器支持限制需要注意?

MDN 标注 @layer 自 2022 年 3 月起已被各浏览器广泛支持。WebKit 文档记载了 Safari 15.4 对层叠层的支持。这些日期说明了该功能的可用时间,并不能保证每台 Mac 都已安装支持它的浏览器。请检查页面实际需要支持的浏览器版本。MDN:@layer、WebKit:Safari 15.4 中的 WebKit 新功能。

不理解层代码块的浏览器不会将其中的样式作为普通未分层规则应用。根据 CSS 解析规则,不受支持的语法会被丢弃。因此,将所有必要 CSS 都放进层中,并不能为较旧的浏览器提供回退方案。MDN:CSS 错误处理。

如果仍需支持这些浏览器,就为该支持路径保留一份有意保持未分层的样式表,并通过选择器和源代码顺序解决冲突。在支持层的浏览器中,避免同时加载未分层的重复版本:这会改变你刚刚设计的层叠关系。

从最小范围的迁移开始,让新组件控制其预期属性。先确定层顺序,将相互冲突的旧规则分配到某一层,移除重复的未分层加载,然后检查最终胜出的声明。按此顺序操作,可以在迁移更多 CSS 之前,清楚地看到每一处优先级变化。

来源