Kodokon kodokon.com

级联层(@layer)与 @scope

掌握两个新的级联判定标准——层与作用域邻近度——从而在不打特异性大战的情况下架构你的样式。

10 分钟 · 3 题

在 Kodokon 中打开本课

级联通过一系列有序的判定标准来解决相互冲突的声明:来源与重要性,然后是上下文,然后是 style 属性内的样式,然后是@layer),然后是特异性,最后是出现顺序。关键在于:层的评估在特异性之前。因此,位于更高优先级层中的一条 .button 规则,会击败位于更低优先级层中的 #nav div.menu a.button——后者 (1,2,2) 的特异性在层顺序面前毫无价值。这正是 @layer 在架构设计系统时如此强大的原因:优先级只需在结构上声明一次,而不必逐个选择器地去争夺。

CSS
@layer reset, base, components, utilities;

@layer components {
  .button { background: rebeccapurple; }
}

@layer base {
  button { background: gray; }
}
开头的声明固定了顺序:无论文件中各代码块的顺序如何,components 都胜过 base。

有两个你绝对需要了解的规范细节。其一:位于任何层之外的样式会击败层内样式(对于普通声明而言)——它们构成一个被置于最后的隐式层。因此你本地的覆盖始终会击败已分层的库。其二:一旦使用 !important,顺序会完全反转。最先声明的层获胜,而层内的 !important 会击败任何层之外的 !important。你 reset 层里的一条 !important 声明会变得几乎不可战胜。

CSS
@layer base, theme;

@layer base {
  a { color: black !important; }
}

@layer theme {
  a { color: purple !important; }
}
使用 !important 时顺序反转:black 获胜,因为 base 是最先声明的。

@scope 会把一组规则限定在一个作用域根和一个可选的下边界之间:@scope (.card) to (.card__content) 会命中那些是 .card 后代、但位于 .card__content 之外的元素——这就是著名的甜甜圈作用域,非常适合在不泄漏到注入内容的前提下为组件的外框设置样式。在其内部,:scope 指代根本身。而且 @scope 引入了一个全新的级联判定标准:邻近度。在特异性相等时,作用域根离元素最近的那条规则获胜——这是 BEM 和组合器都从来无法表达的能力。

CSS
@scope (.card) to (.card__content) {
  :scope {
    padding: 1rem;
    border: 1px solid oklch(0.85 0.02 260);
  }
  img { border-radius: 8px; }
}
.card 外框中的 img 元素会被命中;注入到 .card__content 内的则在作用域之外。

知识检测

确认你已牢记本课的重点内容。

  1. 有 @layer a, b; 以及两条特异性相等的普通声明,一条在 a 中,一条在 b 中,哪一条生效?
    • a 层中那条,因为它最先声明
    • b 层中那条,因为最后声明的层获胜
    • 文件中出现最晚的那条,忽略层
  2. 一条位于任何层之外的普通声明遇上一条置于某层中的普通声明。结果如何?
    • 层内声明始终获胜
    • 未分层的声明获胜
    • 由选择器特异性决定
    • 浏览器报告冲突
  3. 在 @scope (.card) to (.card__content) 中,内部规则可以命中哪些元素?
    • .card 的全部后代,无一例外
    • .card 中位于 .card__content 之外的后代(甜甜圈作用域)
    • 只有 .card__content 及其子元素