样式查询使用入门

尤娜·克拉韦茨 (Una Kravets)
Una Kravets

最近,所有现代浏览器引擎都支持查询父级的内嵌大小和容器查询单元值的功能。

浏览器支持

  • 105
  • 105
  • 110
  • 16

来源

不过,包含规范不仅仅包含尺寸查询,它还支持查询父级的样式值。从 Chromium 111 开始,您将能够为自定义属性值应用样式包含,以及查询父元素中的自定义属性值。

浏览器支持

  • 111
  • 111
  • x
  • x

来源

这意味着我们可以对 CSS 中的样式进行更合理的逻辑控制,并能够更好地将应用逻辑和数据层与其样式分离开来。

CSS 包含模块第 3 级规范(涵盖大小和样式查询)允许从父级查询任何样式,包括 font-weight: 800 等属性和值对。不过,在此功能推出后,样式查询目前只能使用 CSS 自定义属性值。这对于组合样式以及将数据与设计分离出来仍然非常有用。我们来看看如何结合使用样式查询和 CSS 自定义属性:

样式查询使用入门

假设我们有以下 HTML:

<ul class="card-list">
  <li class="card-container">
    <div class="card">
      ...
    </div>
  </li>
</ul>

要使用样式查询,您必须先设置一个容器元素。这需要采用略有不同的方法,具体取决于您要查询的是直接父级还是间接父级。

查询直接父级

样式查询的图表。

与样式查询不同,您无需使用 container-typecontainer 属性将包含在 .card-container 中,即可让 .card 查询其直接父级的样式。不过,我们确实需要将样式(在本例中为自定义属性值)应用于容器(在本例中为 .card-container)或包含您要在 DOM 中设置样式的元素的任何元素。我们无法将正在查询的样式应用于正在使用该查询设置样式的直接元素,因为这可能会导致无限循环。

如需直接查询父级,您可以编写:

/* styling .card based on the value of --theme on .card-container */
@container style(--theme: warm) {
  .card {
    background-color: wheat;
    border-color: brown; 
    ...
  }
}

您可能已经注意到,样式查询使用 style() 封装了查询。这是为了消除尺寸值与样式的歧义。例如,您可以将针对容器宽度的查询编写为 @container (min-width: 200px) { … }。如果父级容器的宽度至少为 200 像素,则会应用样式。不过,min-width 也可以是 CSS 属性,您可以使用样式查询来查询 min-width 的 CSS 值。因此,您需要使用 style() 封装容器来明确区分开来:@container style(min-width: 200px) { … }

设置非直接父级元素的样式

如果您想查询任何非直接父元素的样式,则需要为该元素提供 container-name。例如,我们可以根据 .card-list 的样式将样式应用于 .card,只需为 .card-list 赋予 container-name 并在样式查询中引用它即可。

/* styling .card based on the value of --moreGlobalVar on .card-list */
@container cards style(--moreGlobalVar: value) {
  .card {
    ...
  }
}

一般而言,最佳做法是指定容器名称,让容器能够清晰识别您所查询的内容,并让用户能够更轻松地访问这些容器。例如,如果您想直接为 .card 中的元素设置样式,便可利用此功能。如果没有 .card-container 上的已命名容器,用户就无法直接查询该容器。

但所有这些在实践中都会更有意义。我们来看一些示例:

样式查询的实际运用

演示图片,包含多张产品卡片,部分带有“全新”或“低库存”标签,以及带有红色背景的“低库存”卡片。

如果您具有具有多个变体的可重复使用组件,或者无法控制所有样式但在某些情况下需要应用更改,则样式查询特别有用。此示例展示了一组共用同一卡片组件的产品卡片。某些商品卡片包含由名为 --detail 的自定义属性触发的其他详细信息/备注,例如“新品”或“库存不理想”。此外,如果商品处于“低库存”状态,则该商品具有深红色的边框背景。此类信息可能是由服务器呈现的,并可通过内嵌样式应用于卡片,如下所示:

 <div class="product-list">
  <div class="product-card-container" style="--detail: new">
    <div class="product-card">
      <div class="media">
        <img .../>
      <div class="comment-block"></div>
    </div>
  </div>
  <div class="meta">
    ...
  </div>
  </div>
  <div class="product-card-container" style="--detail: low-stock">
    ...
  </div>
  <div class="product-card-container">
    ...
  </div>
  ...
</div>

有了这些结构化数据,您可以将值传递给 --detail,并使用以下 CSS 自定义属性应用样式:

@container style(--detail: new) {
  .comment-block {
    display: block;
  }
  
  .comment-block::after {
    content: 'New';
    border: 1px solid currentColor;
    background: white;
    ...
  }
}

@container style(--detail: low-stock) {
  .comment-block {
    display: block;
  }
  
  .comment-block::after {
    content: 'Low Stock';
    border: 1px solid currentColor;
    background: white;
    ...
  }
  
  .media-img {
    border: 2px solid brickred;
  }
}

通过以上代码,我们可以为 --detail: low-stock--detail: new 应用条状标签,但您可能已经注意到代码块中存在一些冗余。目前,无法通过 @container style(--detail) 仅查询 --detail 是否存在,这样可以更好地共享样式并减少重复次数。工作组目前正在讨论此功能。

天气卡片

上例使用具有多个可能值的单个自定义属性来应用样式。但您也可以通过使用和查询多个自定义属性来混淆。以下面的天气卡片为例:

天气卡片演示。

如要设置这些卡片的背景渐变和图标样式,请查看天气特征(例如“多云”“有雨”或“晴”):

@container style(--sunny: true) {
  .weather-card {
    background: linear-gradient(-30deg, yellow, orange);
  }
  
  .weather-card:after {
    content: url(<data-uri-for-demo-brevity>);
    background: gold;
  }
}

这样,您就可以根据每张卡片的独特特征对其进行样式设置。不过,您也可以使用 and 组合器为特征(自定义属性)组合设置样式,具体方式与为媒体查询操作相同。例如,一个既多云又晴朗的日子如下所示:

@container style(--sunny: true) and style(--cloudy: true) {
    .weather-card {
      background: linear-gradient(24deg, pink, violet);
    }
  
  .weather-card:after {
      content: url(<data-uri-for-demo-brevity>);
      background: violet;
  }
}

将数据与设计分离

这两个演示都具有将数据层(将在网页上呈现的 DOM)与应用的样式分离开来的结构优势。样式被编写为存在于组件样式中的可能变体,而端点可以发送随后用于为组件设置样式的数据。您可以使用单个值(例如,在前一种情况下更新 --detail 值)或多个变量(如在第二种情况下(设置 --rainy--cloudy--sunny)。最好的一点是,您也可以结合使用这些值,检查 --sunny--cloudy 可能会显示局部多云风格。

可通过 JavaScript 无缝更新自定义属性值,既可以在设置 DOM 模型时(即在框架中构建组件时)完成,也可以随时使用 <parentElem>.style.setProperty('--myProperty’, <value>) 进行更新。I

下面的演示只需几行代码,即可更新按钮的 --theme,并使用样式查询和该自定义属性 (--theme) 应用样式:

使用样式查询为卡片设置样式,用于更新自定义属性值的 JavaScript 如下所示:

const themePicker = document.querySelector('#theme-picker')
const btnParent = document.querySelector('.btn-section');

themePicker.addEventListener('input', (e) => {
  btnParent.style.setProperty('--theme', e.target.value);
})

本文详述的功能只是一个开始。容器查询可为您带来更多益处,帮助您构建动态、响应式界面。具体而言,对于样式查询,仍然存在一些未解决的问题。一个是,在自定义属性之外,针对 CSS 样式实现样式查询。这已属于当前规范级别,但尚未在任何浏览器中实现。布尔值上下文评估预计会在待解决的问题得到解决后添加到当前的规范级别,而范围查询则会被计划用于相应规范的下一级别。