CSS @scope

新的@scope 规则来了!这是一种更好的方法来保持我们的组件样式封闭 - 而无需依赖第三方工具或特殊的命名约定。

作用域样式一直是 CSS的主要目标。选择器将声明范围限定在匹配的元素上。这些选择器可以组合在一起,以创建更具体的范围 - 每个范围通过其与其他范围的关系进行修改。但是,已经在 谷歌浏览器中提供的 @scope功能将使该功能变得更加强大。

在历史上,关系选择器在表达范围方面受到限制。像 > ‘child’组合选择器这样的狭窄关系,如果我们在子元素周围添加了包裹元素,它们就会破坏。而像‘descendant’组合选择器这样的广泛关系很容易渗入嵌套内容。当我们确实想要表示‘直接子代’或‘所有后代’时,这样做是可以的 - 但通常,我们试图表达的是更抽象的归属概念。

一些样式是全局的,但许多样式属于组件。当我们选择 article .title 时,我们不一定想要所有 article内部的 .title元素,而是特定于 article.title。这就是人们在 CSS 中要求作用域时的含义。选择器不仅传达嵌套关系,还传达归属关系。

作用域黑科技

要在 CSS 中表示归属关系,我们需要两个部分 - 正如 BEM所称的‘块’和‘元素’。元素属于块。为了使这种关系比简单的嵌套更清晰,BEM 要求将块名称添加到作用域中的每个元素和选择器中:

/* 嵌套:块中的所有匹配元素 */
.block .element { … }

/* BEM:只有属于块的元素 */
.block-element { … }

现在,大多数前端框架都提供了作用域样式,会自动为元素生成唯一的块名称并将其追加到元素中:

/* 框架(细节会有所不同) */
.element[data-scope=block] { … }

但是命名约定需要组织中的严格遵守,并且自动化需要在单个构建步骤中完全控制HTMLCSS输出。这是一个侵入性的过程,它会在整个 DOM 中应用唯一的属性,并将它们追加到每个相关选择器中。

这些都不是解决问题的可靠方案;这些是我们在过去习惯了的黑科技。

CSS @scope介绍

新的 CSS @scope 规则允许我们以两个部分定义作用域关系。首先,我们通过选择给定作用域的‘根’来定义块本身。这些选择器放在规则的‘前奏’中:

@scope (.block) { … }

然后,我们可以定义属于该块作用域的‘作用域’选择器:

@scope (.block) {
  .element { … }
}

如果你使用过 CSS嵌套,这可能会很熟悉。它将选择与以下任一相同的元素:

.block {
  .element { … }
}

.block .element { … }

在嵌套中,每个嵌套选择器之前都有一个隐式的&,但如果需要的话,你也可以明确地放置&& 选择器是嵌套父级的占位符:

.block {
  .element { /* 隐式开始 & */ }
  & .element { /* 显式开始 & */ }
  .context & { /* 重新定位 & */ }
}

作用域的工作方式相同,但默认使用 :scope 选择器:

@scope (.block) {
  .element { /* 隐式开始 :scope */ }
  :scope .element { /* 显式开始 :scope */ }
  .context :scope { /* 重新定位 :scope */ }
}

它们看起来相似,但不要被表面相似所迷惑。CSS 嵌套是编写多部分(复杂)选择器的一种简写。但尽管是以多个步骤编写的,最终的选择器只匹配一个元素:组合选择器的最终‘主体’。在选择过程中,可以引用嵌套关系来缩小我们的主体范围,但这些关系在选择完成后不会‘保留下来’。

为了使作用域在层叠中更有意义,我们需要知道我们正在样式化的元素以及它所属的块。作用域规则通过将选择显式地分成两个不同的部分,并为每个部分指定不同的目标来实现这一点:作用域根选择器和主体元素选择器。这个微妙的变化产生了巨大的不同。

作用域关联度

因为我们知道主体与其所属作用域之间的关系,我们可以做一些事情,比如测量它们的关联度 - 即它们之间的 DOM步骤数:

<article class='block'>
  <p class='element'>
    距离 .element 到 .block 只有 1 步。
  </p>
  <footer>
    <div>
      <p class='element'>
        从 .element 到 .block 有 3 步(通过 div 和 footer)。
      </p>
    </div>
  </footer>
</article>

作用域关联度已经在选择器的特异性之后,但在出现顺序之前添加到了层叠中。当两个具有相同特异性的选择器应用于同一个元素时,具有“更近”的作用域根的选择器将获胜。

<h1>Scope Proximity</h1>
<p class="support">
  ⚠️ Your browser does not support the <code>@scope</code> rule.
  Try viewing this demo in Chrome Canary 
  with the <strong>experimental web platform features</strong> flag.
</p>
<div class="dark-theme">
<!--   <h2>with scope</h2> -->
  <p>The dark-theme link should be <a href="#">lightcyan</a></p>

  <div class="light-theme">
    <p>The light-theme link should be <a href="#">mediumvioletred</a></p>
    
    <div class="dark-theme">
      <p>The dark-theme link should be <a href="#">lightcyan</a></p>
    </div>
  </div>
  
  <p>
    With <code>@scope</code>,
    we can ensure that the 'nearer' scope wins,
    giving us the correct result no matter how these scopes are nested.</p>
</div>

<!-- <div class="no-scope dark-theme">
  <h2>no scope</h2>
  <p>The dark-theme link should be <a href="#">lightcyan</a></p>

  <div class="no-scope light-theme">
    <p>The light-theme link should be <a href="#">mediumvioletred</a></p>

    <div class="dark-theme">
      <p>The dark-theme link should be <a href="#">lightcyan</a></p>
    </div>
  </div>
  
  <p>
    Without <code>@scope</code>, 
    the dark-theme link color will always win 
    since it is defined later in the CSS,
    with the same specificity.
  </p>
</div>

<p>
  Scope <strong>proximity</strong>
  helps resolve conflicts when two scopes overlap.
  In this case, the nested/overlapping light and dark themes
  both define link colors using the same selector
  (and same specificity).
  Without scope, the later rule takes precedence.
  With scope, the 'closer' scope root has priority.
</p> -->
/* without scopes */
/* remove these in a supporting browser */
.light-theme a { color: mediumvioletred; }
.dark-theme a { color: lightcyan; }

/* with scopes */
@scope (.light-theme) {
  :scope {
    background: white;
    color: black;
  }
  a { color: mediumvioletred; }
}

@scope (.dark-theme) {
  :scope {
    background: black;
    color: white;
  }
  a { color: lightcyan; }
}

@scope (html) {
  .support { display: none; }
}


.light-theme,
.dark-theme {
  padding: 1em;
  margin: 1em 0;
  border: thin solid;
}

a:any-link { 
  border-left: 1em solid currentcolor;
  padding-left: 0.4em;
}

/* without scopes */
.no-scope.light-theme {
  background: white;
  color: black;
}

.no-scope.dark-theme {
  background: black;
  color: white;
}

.no-scope.light-theme a:any-link { color: mediumvioletred; }
.no-scope.dark-theme a:any-link { color: lightcyan; }

.support {
  border: medium solid red;
  color: maroon;
  padding: 1em;
}

body {
  padding: 1em;
  margin: 0 auto;
  max-width: 70ch;
}

这在两个选择器具有相同目的并且我们可能希望有广泛重叠的作用域的情况下特别有用 - 比如从不同主题设置链接颜色。但还存在其他实现类似结果的方法,比如自定义属性(根据接近性继承)。当我们将第三个选择器添加到语法中时,事情会变得更加有趣。

作用域边界

基于组件的方法的真正强大之处在于通过以各种方式组合块来组合新模式的能力。'标签页'组件只有在我们将其他内容放在每个标签面板内时才有用。然而,标签元素不知道该内容将是什么,因此我们的'标签'组件样式的作用域应该在内容开始的地方停止。当组件中间有一个洞(或一个插槽),用于放置其他内容

这对于嵌套选择器来说会造成问题,因为它们同样适用于所有后代。因此,我们依赖于第三方工具和 BEM 语法,以生成唯一的标识符,并将其应用于洞中的每个元素。@scope规则使得这一点成为可能,而无需任何约定或工具。我们可以在前奏中提供第二个选择器:

@scope (.block) to (.slot) {
  .element { … }
}

您可以在任何最新版本的 谷歌浏览器中看到这项工作:

<article class="media">
  <img src="https://assets.codepen.io/15542/mia.jpg" alt="Miriam speaking" height="300" width="300" />
  <div class="inner">
    <h2 class="title">Outer Title is in-scope</h2>
    <p>
      This title, image,
      and paragraph fall <em>between</em>
      a scope-root (<code>.media</code>)
      and lower-boundary (<code>.content</code>),
      so the scoped styles apply.
    </p>
    <div class="content">
      <h3 class="title">Inner Title is out-of-scope</h3>
      <img src="https://assets.codepen.io/15542/sad.jpg" alt="girl, pouting" width="2220" height="1248" />
      <p>
        This image, title, and paragraph
        are inside (<code>.media</code>),
        but there is an intervening boundary
        (<code>.content</code>)
        that ends the scope.
      </p>
    </div>
  </div>
</article>
/* applies to both images */
img {
  max-width: 100%;
  height: auto;
}

@scope (.media) to (.content) {
  /* only the outer image, between media and content */
  img {
    border-radius: 100%;
  }

  .title,
  p {
    color: rebeccapurple;
  }

  /* layout */
  :scope {
    background: lightcyan;
    border: medium solid;
    padding: 1em;
    display: grid;
    grid-template: "media content" auto / minmax(14ch, 20%) minmax(
        min-content,
        1fr
      );
    gap: 1em;
  }

  img {
    grid-area: media;
  }

  .inner {
    grid-area: content;
  }
}

.content {
  background: white;
  border: medium dotted;
  padding: 1em;
}

html {
  height: 100%;
  display: grid;
  place-content: safe center;
}

body {
  max-width: 75ch;
}


:not() 选择器有一个很酷的技巧,可以让你接近,但是如果开始嵌套作用域,它就会失败。创建一个孤立的代码块需要理解页面上每个根、边界和主体元素之间的 DOM 关系 - 因此需要三个不同的选择器来使其工作。

作用域规则保持权重

嵌套和 @scope 之间的另一个重要区别是选择器的优先级权重。嵌套选择器被合并为一个,具有相应的优先级权重:

#banner {
  /* 优先级权重:[1, 0, 1] */
  h1 { color: hotpink; }
}

但是使用作用域时,我们使用两个或三个不同的选择器。它们永远不会合并在一起,因此不会导致组合优先级权重:

@scope (#banner) {
  /* 优先级权重:[0, 0, 1] */
  h1 { color: hotpink; }
}

许多作者喜欢使用 BEM 语法通过使用单个类来“平铺”权重。但选择只是CSS 的一半!如果我们只能使用类,那么我们就放弃了语言中一些最强大和表达力最强的功能。通过在合适的时候使用作用域而不是嵌套,我们可以兼得两者的优点!

使用显式的 :scope 选择器会增加一些优先级权重 - 伪类具有与普通类相同的优先级权重。它引用了作用域根元素,但没有直接引用作用域根选择器。如果我们确实想要组合这两个选择器并获得它们的组合优先级权重,我们可以使用 &代替:

@scope (#banner) {
  /* 权重:[0, 1, 1] */
  :scope h1 { color: hotpink; }

  /* 权重:[1, 0, 1] */
  & h1 { color: hotpink; }
}

隐式作用域和嵌入样式

有时,直接在页面中与其样式的组件一起嵌入样式可能会很有用。在那里也可以使用作用域!没有定义根选择器的@scope 规则将使用父元素作为隐式作用域根:

<section>
  <style>
    @scope to (article) {
      :scope { 
        border: thin dotted mediumvioletred; 
        padding: 1em;
      }
      
      .title { 
        font-family: fantasy;
        
        &::before { content: '✅ '; }
      }
      
      p { font-style: italic; }
    }
  </style>
  <h2 class="title">Title in the section scope</h2>
  <p>This paragraph is also in the section scope.</p>
  <hr>
  <article>
    <h3 class="title">Nested article titles are not in scope</h3>
    <p>
      Paragraphs inside the article
      are also out-of-scope!
      Yay for lower boundaries!
      Yay for implicit scopes!
    </p>
  </article>
</section>

总结

  • 使用 @scope 定义特定的模式或组件及其块元素关系。这些可以是广泛的(整个主题)或狭窄的(单个按钮),有时可能会重叠。元素属于多个作用域是可以的,但每个作用域都特定于 DOM 的某个片段。

  • 嵌套存在是为了使复杂的选择器更易读。它可以处理比 @scope 更广泛的选择器,因为它并不是设计用于一个特定的用例。虽然 @scope 在表达块元素关系方面表现出色,但在伪类等元素 - 修改器关系方面,嵌套仍然是明确的选择。

  • 使用@layer 对不同的样式关注点进行分组和优先级排序。它们往往更广泛和建筑性 - 重置、框架、设计系统和实用程序 - 并适用于任意数量的组件。按钮组件和主题组件可能都从“默认”层开始。

  • 作用域根通常是 @container查询的良好容器元素。我预计在许多 @scope 规则的顶部将经常看到 :scope { container: my-scope-name / inline-size; }。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 155,704评论 4 358
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,084评论 1 286
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 105,560评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,360评论 0 201
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,686评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,150评论 1 204
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,528评论 2 306
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,256评论 0 193
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,878评论 1 235
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,215评论 2 239
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,757评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,121评论 2 249
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,659评论 3 228
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,932评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,638评论 0 191
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,159评论 2 263
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,112评论 2 258

推荐阅读更多精彩内容