深入容器样式查询

Avatar of Geoff Graham
Geoff Graham

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 免费信用额度!

我之前写了一些关于 容器样式查询的早期想法。现在还处于早期阶段。它们已经在 CSS Containment Module Level 3 规范(目前处于编辑草案状态)中定义,但仍有一些讨论正在进行。

基本思想是我们可以定义一个容器,然后根据其计算的样式有条件地将其后代应用样式。

@container <name>? <conditions> {
  /* conditional styles */
}

到目前为止,我见过的最好的例子是在内容已用斜体的情况下,从类似于 <em><i><q> 的内容中删除斜体。

em, i, q {
  font-style: italic; /* default UA behavior */
}

/* When the container's font-style is italic, remove italics from these elements. */
@container style(font-style: italic) {
  em, i, q {
    font-style: normal;
  }
}

这就是总体思路。但如果你不知道,规范的编辑 Miriam Suzanne 持续维护着一组完整的 容器样式查询的个人笔记,这些笔记公开可用。它前几天更新了,我在里面花了一些时间试图理解样式查询更细微的方面。这些都是非官方的东西,但我认为我应该记下一些让我印象深刻的东西。谁知道呢?也许这是我们将来可以期待的东西!

每个元素都是一个样式容器

我们甚至不需要显式分配 container-namecontainer-type 来定义一个样式容器,因为默认情况下,所有元素都是样式容器。

因此,您在上面看到的删除斜体的示例?请注意它没有识别容器。它直接跳到使用 style() 函数的查询。那么,正在查询哪个容器呢?它将是**接收应用样式的元素的直接父级**。如果不是这样,那么它就是**优先级最高的下一个最接近的相对容器**。

我喜欢这一点。查询向上搜索匹配项,然后继续冒泡直到找到匹配条件,这非常符合 CSS 的风格。

我难以理解为什么我们可以使用基于样式的隐式容器,但在处理尺寸查询(如 sizeinline-size)时却不行。Miriam 对此做了很好的解释

尺寸查询需要在容器的大小、布局和样式上进行 css 包含,以防止布局循环。包含是广泛应用的侵入性操作,因此作者必须小心地控制哪些元素是(或不是)大小容器。

基于样式的查询没有相同的限制。在 CSS 中,后代样式无法影响祖先的计算样式。因此,不需要包含,并且将元素设置为 样式查询容器 不会产生任何侵入性或意外的副作用。

(强调我的)

这一切都归结于后果——就默认情况下所有元素都是样式查询容器而言,没有后果。

  • 如果找到了容器:条件将针对该容器进行解析。
  • 如果多个容器匹配:优先使用最接近的相对容器。
  • 如果未找到匹配项:返回 unknown

这与 CSS 剩余部分的 “宽容”精神相同。

容器可以同时支持尺寸查询和样式查询

假设我们想要在没有显式 container-name 的情况下定义样式查询

@container style(font-style: italic) {
  em {
    font-style: normal;
  }
}

之所以有效,是因为 所有元素都是样式容器,无论 container-type 是什么。这就是我们可以隐式查询样式并依赖最接近匹配项的原因。这完全没问题,因为同样地,在创建样式容器时,不会产生任何负面影响。

对于尺寸查询,我们必须使用显式 container-type,但对于样式查询则不必,因为每个元素都是一个样式查询。这也意味着该容器既是样式容器,也是尺寸查询容器

.card-container {
  container: card / inline-size; /* implictly a style query container as well */
}

排除容器被查询

也许我们不希望容器参与匹配过程。在这种情况下,可以在元素上设置 container-type: none

.some-element {
  container-type: none;
}

显式样式查询容器提供对查询内容的更多控制

假设我们要编写针对 padding 的样式查询,无论我们使用的是显式命名的容器还是最接近的直接父级,都没有可靠的方法来确定最佳匹配容器。这是因为 padding 不是继承的属性。

因此,在这些情况下,我们应该使用 container-name 来显式告知浏览器可以从哪些容器中获取数据。我们甚至可以为容器提供多个显式名称,以使其匹配更多条件。

.card {
  container-name: card layout theme;
}

哦,container-name 接受容器的任意数量的可选且 可重用 名称!在帮助浏览器在搜索匹配项时做出选择方面,这提供了更大的灵活性。

.theme {
  container-name: theme;
}
.grid {
  container-name: layout;
}
.card {
  container-name: card layout theme;
}

我有点想知道,如果一个容器被忽略,这是否也可以被视为一种“回退”。

样式查询可以组合

orand 运算符允许我们组合查询,以保持代码简洁

@container bubble style(--arrow-position: start start) or style(--arrow-position: end start) {
  .bubble::after {
    border-block-end-color: inherit;
    inset-block-end: 100%;
  }
}

/* is the same as... */
@container bubble style(--arrow-position: start start) {
  /* etc. */
}
@container bubble style(--arrow-position: end start) {
  /* etc. */
}

切换样式

容器样式查询和 正在进行的定义 toggle() 函数的工作之间有一些重叠。例如,我们可以循环遍历两个 font-style 值,例如 italicnormal

em, i, q {
  font-style: italic;
}

@container style(font-style: italic) {
  em, i, q {
    font-style: normal;
  }
}

很酷。但是 CSS 切换的提案建议 toggle() 函数将是一种更简单的方法

em, i, q {
  font-style: toggle(italic, normal);
}

但是,除了这种二进制用例之外,toggle() 不太适用。然而,样式查询可以正常工作。Miriam 确定了三种情况下,样式查询比 toggle() 更合适

/* When font-style is italic, apply background color. */
/* Toggles can only handle one property at a time. */
@container style(font-style: italic) {
  em, i, q {
    background: lightpink;
  }
}

/* When font-style is italic and --color-mode equals light */
/* Toggles can only evaluate one condition at a time */
@container style((font-style: italic) and (--color-mode: light)) {
  em, i, q {
    background: lightpink;
  }
}

/* Apply the same query condition to multiple properties */
/* Toggles have to set each one individually as separate toggles */
@container style(font-style: italic) {
  em, i, q {
    /* clipped gradient text */
    background: var(--feature-gradient);
    background-clip: text;
    box-decoration-break: clone;
    color: transparent;
    text-shadow: none;
  }
}

样式查询解决了“自定义属性切换技巧”

请注意,样式查询是针对 “CSS 自定义属性切换技巧”的正式解决方案。在其中,我们设置了一个空的自定义属性 (--foo: ;),并使用逗号分隔的回退方法,当自定义属性设置为实际值时,打开和关闭属性。

button {
  --is-raised: ; /* off by default */
  
  border: 1px solid var(--is-raised, rgb(0 0 0 / 0.1));
  box-shadow: var(
    --is-raised,
    0 1px hsl(0 0% 100% / 0.8) inset,
    0 0.1em 0.1em -0.1em rgb(0 0 0 / 0.2)
  );
  text-shadow: var(--is-raised, 0 -1px 1px rgb(0 0 0 / 0.3));
}

button:active {
  box-shadow: var(--is-raised, 0 1px 0.2em black inset);
}

#foo {
  --is-raised: initial; /* turned on, all fallbacks take effect. */
}

这超级酷,也是样式容器查询简化的许多工作。

样式查询和 CSS 生成的内容

对于由 ::before::after 伪元素的 content 属性生成的生成内容,匹配容器是生成该内容的元素。

.bubble {
  --arrow-position: end end;
  container: bubble;
  border: medium solid green;
  position: relative;
}

.bubble::after {
  content: "";
  border: 1em solid transparent;
  position: absolute;
}

@container bubble style(--arrow-position: end end) {
  .bubble::after {
    border-block-start-color: inherit;
    inset-block-start: 100%;
    inset-inline-end: 1em;
  }
}

样式查询和 Web 组件

我们可以将 Web 组件定义为一个容器,并通过样式对其进行查询。首先,我们有组件的<template>

<template id="media-host">
  <article>
    <div part="img">
      <slot name="img">…</slot>
    </div>
    <div part="content">
      <slot name="title">…</slot>
      <slot name="content">…</slot>
    </div>
  </article>
</template>

然后,我们使用:host 伪元素作为容器来设置container-namecontainer-type 以及一些高级属性

:host {
  container: media-host / inline-size;
  --media-location: before;
  --media-style: square;
  --theme: light;
}

<media-host> 内部的元素可以查询<media-host> 元素的参数

@container media-host style(--media-style: round) {
  [part='img'] {
    border-radius: 100%;
  }
}

接下来呢?

再次强调,这里记录的所有内容都基于 Miriam 的笔记,这些笔记并非官方规范的替代品。但它们表明正在讨论的内容,以及未来可能出现的方向。感谢 Miriam 提供了一些正在进行的讨论,我们可以关注这些讨论,以了解最新进展。