@scope

@scope CSS @ 规则使你能够选择特定 DOM 子树中的元素,从而精确定位元素,而无需编写难以覆盖的、过于具体的选择器,也无需将选择器与 DOM 结构过于紧密地耦合。

在 JavaScript 中,可以通过 CSS 对象模型接口 CSSScopeRule 访问 @scope

语法

@scope @ 规则包含一个或多个规则集(称为作用域样式规则),并定义了将它们应用于所选元素的作用域。@scope 有两种使用方式:

  1. 作为 CSS 中的独立块,这种情况下它包含一个序言部分,其中包括作用域根和可选的作用域限制选择器——它们定义了作用域的上下边界。

    css
    @scope (scope root) to (scope limit) {
      /* … */
    }
    
  2. 作为 HTML 中 <style> 元素内的内联样式,这种情况下序言被省略,其包含的规则集会自动限定在 <style> 元素的父元素范围内。

    html
    <parent-element>
      <style>
        @scope {
          /* rulesets */
        }
      </style>
    </parent-element>
    

    也可以将内联 @scope 与作用域限制选择器结合使用,如 @scope to (scope limit) { ... }

描述

一个复杂的 Web 文档可能包含页眉、页脚、新闻文章、地图、媒体播放器、广告等组件。随着复杂性的增加,有效管理这些组件的样式变得越来越重要,而有效的作用域样式有助于我们管理这种复杂性。让我们看下面的 DOM 树:

body
└─ article.feature
   ├─ section.article-hero
   │  ├─ h2
   │  └─ img
   │
   ├─ section.article-body
   │  ├─ h3
   │  ├─ p
   │  ├─ img
   │  ├─ p
   │  └─ figure
   │     ├─ img
   │     └─ figcaption
   │
   └─ footer
      ├─ p
      └─ img

如果你想选择类名为 article-body<section> 元素内的 <img> 元素,可以这样做:

  • 编写像 .feature > .article-body > img 这样的选择器。然而,这个选择器特异性很高,难以覆盖,并且与 DOM 结构紧密耦合。如果将来标记结构发生变化,你可能需要重写 CSS。
  • 编写像 .article-body img 这样不那么具体的选择器。然而,这会选中该 section 内的所有图片。

这就是 @scope 发挥作用的地方。它允许你定义一个精确的作用域,在此作用域内你的选择器可以定位元素。例如,你可以使用一个独立的 @scope 块来解决上述问题,如下所示:

css
@scope (.article-body) to (figure) {
  img {
    border: 5px solid black;
    background-color: goldenrod;
  }
}

.article-body 作用域根选择器定义了规则集将要应用的 DOM 树作用域的上限,而 figure 作用域限制选择器定义了下限。因此,只有在类名为 article-body<section> 内,但不在 <figure> 元素内的 <img> 元素才会被选中。

备注: 这种具有上下边界的作用域通常被称为甜甜圈作用域

作用域的上限是包含的,而下限是排除的。要改变这种行为,你可以将任一选择器与通用子代选择器结合。例如,@scope (scope root) to (scope limit > *) 会使上下边界都包含,@scope (scope root > *) to (scope limit) 会使上下边界都排除,而 @scope (scope root > *) to (scope limit > *) 会得到一个排除的上限和一个包含的下限。

如果你想选择类名为 article-body<section> 内的所有图片,可以省略作用域限制:

css
@scope (.article-body) {
  img {
    border: 5px solid black;
    background-color: goldenrod;
  }
}

或者,你可以将 @scope 块内联到 <style> 元素中,该元素又位于类名为 article-body<section> 内部:

html
<section class="article-body">
  <style>
    @scope {
      img {
        border: 5px solid black;
        background-color: goldenrod;
      }
    }
  </style>

  <!-- ... -->
</section>

备注: 必须理解的是,虽然 @scope 允许你将选择器的应用隔离到特定的 DOM 子树中,但它并不能将应用的样式完全隔离在这些子树内。这一点在继承方面最为明显——会被子元素继承的属性(例如 colorfont-family)仍将被继承,超出任何设定的作用域限制。

@scope 块中的 :scope 伪类

@scope 块的上下文中,:scope 伪类提供了一种便捷的方式来直接为作用域根应用样式,就像这样:

css
@scope (.feature) {
  :scope {
    background: rebeccapurple;
    color: antiquewhite;
    font-family: sans-serif;
  }
}

以下是关于在 @scope 块中使用 :scope 的一些注意事项:

  • :scope 增加了类级别的特异性(详情请见@scope 中的特异性)。

  • 作用域限制可以使用 :scope 来指定作用域限制和作用域根之间的特定关系要求。例如:

    css
    /* figure is only a limit when it is a direct child of the :scope */
    @scope (.article-body) to (:scope > figure) {
      /* … */
    }
    
  • 作用域限制可以使用 :scope 引用作用域根之外的元素。例如:

    css
    /* figure is only a limit when the :scope is inside .feature */
    @scope (.article-body) to (.feature :scope figure) {
      /* … */
    }
    
  • 作用域样式规则不能逃逸出子树。像 :scope + p 这样的选择是无效的,因为该选择会超出子树的范围。

  • 将作用域根和限制定义为选择器列表是完全有效的,在这种情况下会定义多个作用域。在下面的例子中,样式会应用于任何类名为 article-heroarticle-body<section> 内的 <img>,但如果它嵌套在 <figure> 内部则不应用:

    css
    @scope (.article-hero, .article-body) to (figure) {
      img {
        border: 5px solid black;
        background-color: goldenrod;
      }
    }
    

@scope 中的特异性

@scope 规则内部,普通选择器和 & 嵌套选择器的行为就像在选择器前加上了 :where(:scope) 一样。因为 :where()特异性为零,所以普通选择器和 & 不增加权重。特异性权重由选择器的其余部分决定。例如,& img 选择器的特异性等同于 :where(:scope) img 的特异性(0-0-1)。

警告: &@scope 块内的特异性处理方式因浏览器引擎和发布版本而异。详情请查看浏览器兼容性

在下面的代码块中,两种情况下的特异性都仅来自 img

css
@scope (.article-body) {
  /* img has a specificity of 0-0-1, as expected */
  img {
    /* … */
  }

  /* & img also has a specificity of 0-0-1 */
  & img {
    /* … */
  }
}

相比之下,显式使用 :scope 会选中作用域根,并增加类级别的特异性(0-1-0),因为 :scope 是一个伪类。在下面的代码块中,:scope img 的特异性为 0-1-1:

css
@scope (.article-body) {
  /* :scope img has a specificity of 0-1-0 + 0-0-1 = 0-1-1 */
  :scope img {
    /* … */
  }
}

@scope 冲突如何解决

@scopeCSS 层叠增加了一个新标准:作用域邻近性。它指出,当两个作用域有冲突的样式时,应用那个与作用域根在 DOM 树层级中“跳数”最少的样式。让我们通过一个例子来看看这意味着什么。

以下面这段 HTML 片段为例,其中不同主题的卡片相互嵌套:

html
<div class="light-theme">
  <p>Light theme text</p>
  <div class="dark-theme">
    <p>Dark theme text</p>
    <div class="light-theme">
      <p>Light theme text</p>
    </div>
  </div>
</div>

如果你像这样编写主题 CSS,就会遇到问题:

css
.light-theme {
  background: #cccccc;
}

.dark-theme {
  background: #333333;
}

.light-theme p {
  color: black;
}

.dark-theme p {
  color: white;
}

最内层的段落本应是黑色的,因为它在一个浅色主题的卡片内。然而,它同时被 .light-theme p.dark-theme p 两个选择器选中。由于 .dark-theme p 规则在源代码中出现得更晚,它被应用了,导致段落被错误地染成了白色。

要解决这个问题,你可以像下面这样使用 @scope

css
@scope (.light-theme) {
  :scope {
    background: #cccccc;
  }
  p {
    color: black;
  }
}

@scope (.dark-theme) {
  :scope {
    background: #333333;
  }
  p {
    color: white;
  }
}

现在,最内层的段落被正确地染成了黑色。这是因为它距离 .light-theme 作用域根只有一个 DOM 树层级,而距离 .dark-theme 作用域根有两个层级。因此,浅色样式胜出。

备注: 作用域邻近性会覆盖源代码顺序,但它本身会被其他更高优先级的标准覆盖,例如重要性特异性

正式语法

@scope = 
@scope [ ( <scope-start> ) ]? [ to ( <scope-end> ) ]? { <block-contents> }

示例

作用域根内的基本样式

在这个例子中,我们使用两个独立的 @scope 块来分别匹配类名为 .light-scheme.dark-scheme 元素内的链接。注意 :scope 是如何被用来选择并为作用域根本身提供样式的。在这个例子中,作用域根是应用了这些类的 <div> 元素。

HTML

html
<div class="light-scheme">
  <p>
    MDN contains lots of information about
    <a href="/en-US/docs/Web/HTML">HTML</a>,
    <a href="/en-US/docs/Web/CSS">CSS</a>, and
    <a href="/en-US/docs/Web/JavaScript">JavaScript</a>.
  </p>
</div>

<div class="dark-scheme">
  <p>
    MDN contains lots of information about
    <a href="/en-US/docs/Web/HTML">HTML</a>,
    <a href="/en-US/docs/Web/CSS">CSS</a>, and
    <a href="/en-US/docs/Web/JavaScript">JavaScript</a>.
  </p>
</div>

CSS

css
@scope (.light-scheme) {
  :scope {
    background-color: plum;
  }

  a {
    color: darkmagenta;
  }
}

@scope (.dark-scheme) {
  :scope {
    background-color: darkmagenta;
    color: antiquewhite;
  }

  a {
    color: plum;
  }
}

结果

以上代码渲染效果如下:

作用域根和作用域限制

在这个例子中,我们有一个 HTML 片段,它与我们在描述部分前面谈到的 DOM 结构相匹配。这个结构代表一个典型的文章摘要。需要注意的关键特征是 <img> 元素,它们嵌套在结构的不同层级中。

这个例子的目的是展示如何使用作用域根和限制来为 <img> 元素设置样式,从层级顶部开始,一直到(但不包括)<figure> 元素内的 <img>——实际上是创建了一个甜甜圈作用域。

HTML

html
<article class="feature">
  <section class="article-hero">
    <h2>Article heading</h2>
    <img alt="image" />
  </section>

  <section class="article-body">
    <h3>Article subheading</h3>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod
      consectetur leo, nec eleifend quam volutpat vitae. Duis quis felis at
      augue imperdiet aliquam. Morbi at felis et massa mattis lacinia. Cras
      pharetra velit nisi, ac efficitur magna luctus nec.
    </p>

    <img alt="image" />

    <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>

    <figure>
      <img alt="image" />
      <figcaption>My infographic</figcaption>
    </figure>
  </section>

  <footer>
    <p>Written by Chris Mills.</p>
    <img alt="image" />
  </footer>
</article>

CSS

在我们的 CSS 中,我们有两个 @scope 块:

  • 第一个 @scope 块将其作用域根定义为类名为 .feature 的元素(在本例中,只有外部的 <article>),展示了如何使用 @scope 为特定的 HTML 子集设置主题。
  • 第二个 @scope 块也将其作用域根定义为类名为 .feature 的元素,但同时定义了一个作用域限制为 figure。这确保了其中包含的规则集只会应用于作用域根(在本例中为 <article class="feature"> ... </article>)内嵌套在后代 <figure> 元素中的匹配元素。这个 @scope 块包含一个单一规则集,为 <img> 元素设置了粗黑边框和金色背景。
css
/* Scoped CSS */

@scope (.feature) {
  :scope {
    background: rebeccapurple;
    color: antiquewhite;
    font-family: sans-serif;
  }

  figure {
    background-color: white;
    border: 2px solid black;
    color: black;
    padding: 10px;
  }
}

/* Donut scope */

@scope (.feature) to (figure) {
  img {
    border: 5px solid black;
    background-color: goldenrod;
  }
}

结果

在渲染出的代码中,请注意除了 <figure> 元素(标记为“我的信息图”)内的那个 <img> 之外,所有的 <img> 元素都被设置了粗边框和金色背景。

规范

规范
CSS 层叠与继承第 6 级
# scoped-styles

浏览器兼容性

css.at-rules.scope

css.selectors.nesting.at-scope

另见