设计令牌响应性

Web 功能: 容器样式查询

背景和概览

作者通常需要根据上下文对组件的设计进行更改。过去,作者需要使用选择器来应用此类更改。这通常意味着,虽然他们的许多设计令牌可以作为自定义属性存在,但高阶设计令牌只能编码为选择器模式(即使用类名或属性约定)或 JavaScript 框架中的 props/context。

容器样式查询允许作者根据祖先元素的计算自定义属性值来设置元素样式。这意味着,作者可以在样式表中编写有意义的设计令牌值,而无需依赖标记或 JavaScript 来呈现。

如何实现

使用容器样式查询实现响应式设计令牌非常简单:

  1. 将高阶设计令牌设置为容器上的自定义属性。此属性无需是已注册的自定义属性。
  2. 使用 @container style() 规则查询该自定义属性的值。
  3. 将适当的样式应用于容器内的后代元素。

您需要注意以下几点:

  • 使用样式查询进行查询的容器不需要设置 container-type 或 container-name,但 container-name 可以实现更具体的查询。
  • 容器本身无法通过容器样式查询进行样式设置。

以下是上述实现步骤的基本示例。

<div class="features">
  <div class="card"></div>
  <div class="card"></div>
</div>
<div class="bugs">
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
</div>
.features {
  --density: spacious;
}

.bugs {
  --density: compact;
}

@container style(--density: compact) {
  .card {
    padding: 8px;
  }
}

@container style(--density: spacious) {
  .card {
    padding: 24px;
  }
}

后备策略

在 Baseline 支持容器样式查询之前,不建议将其用于必须在所有浏览器中都可用的核心功能,因为很难为其创建不会削弱其优势或具有自身局限性的回退方案。例如,如果界面密度用户偏好设置不被视为必须在所有体验中都可用的核心功能,则可以使用容器样式查询来实现该功能,而无需后备。

改用选择器

对于核心功能,应使用选择器作为替代方法。此示例使用 data-density 属性在标记中对密度设计令牌进行编码,而不是将其作为自定义属性:

<div class="features" data-density="spacious">
  <div class="card"></div>
  <div class="card"></div>
</div>
<div class="bugs" data-density="compact">
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
  <div class="card"></div>
</div>
/* This example uses `:where()` to avoid increasing specificity */
:where([data-density="compact"]) .card {
  padding: var(--card-padding-compact);
}

:where([data-density="spacious"]) .card {
  padding: var(--card-padding-spacious);
}

这种回退方法的一个主要限制是,它不支持嵌套设置了 data-density 属性的元素,因为选择器特异性相同,所以将使用出现顺序来确定样式(即 [data-density="spacious"] 始终优先于 [data-density="compact"])。

将样式查询用作渐进增强

虽然我们不建议这样做,但如果您想将样式查询用作核心功能的渐进增强,则可以创建一些自定义属性,然后添加样式查询,以避免重复。确保后备方法在选择容器元素时使用 :where(),以免增加特异性。

.card {
  --card-padding-compact: 8px;
  --card-padding-spacious: 24px;
}

:where([data-density="compact"]) .card {
  padding: var(--card-padding-compact);
}

:where([data-density="spacious"]) .card {
  padding: var(--card-padding-spacious);
}

/* Use style queries as a progressive enhancement: same specificity, so order of appearance is used */

@container style(--density: compact) {
  .card {
    padding: var(--card-padding-compact);
  }
}

@container style(--density: spacious) {
  .card {
    padding: var(--card-padding-spacious);
  }
}

仅使用 CSS 进行功能检查

如果您需要对容器样式查询进行功能检查,以便有条件地显示依赖于它的某些界面,可以使用样式查询来实现:

:root {
  --style-queries-supported: check;
}

.density-toggle {
  display: none;
}

@container style(--style-queries-supported) {
  .density-toggle {
    display: revert;
  }
}

使用 JavaScript 进行功能检查

对于使用 JavaScript 进行功能检查,情况会稍微复杂一些,因为 CSSContainerRule 接口在添加容器样式查询之前就已存在,因此无法可靠地用于此目的。而是需要检查使用样式查询设置的已知属性的计算值。

此示例使用自定义属性,因为它不会产生视觉效果:

:root {
  --style-queries-supported: check;
}

@container style(--style-queries-supported: check) {
  body {
    --style-queries-supported: yes;
  }
}

然后,在 JavaScript 中检查计算出的值,如下所示:

if (getComputedStyle(document.body).getPropertyValue("--style-queries-supported") === "yes") {
  // Use container style queries
} else {
  // Use fallback strategy
}