容器大小查询能帮上我的几个时刻

Avatar of Dan Christofi
Dan Christofi

DigitalOcean 提供适合您旅程各个阶段的云产品。立即开始使用 价值 200 美元的免费积分!

CSS 容器查询正在获得越来越多的关注,我们中的许多人都在尝试使用它,即使只是用于一些小实验。它拥有很棒的浏览器支持,但还不够全面,浏览器支持 — 足以在一些项目中使用它,但可能还不至于让我们尝试用闪亮的容器大小查询来替换过去项目中的媒体查询

不过它们确实很方便!事实上,我已经遇到过几次想要使用它们但无法克服支持要求的情况。如果我能够使用它们,那么在这些情况下它将是这样工作的。

以下所有演示最好在撰写本文时使用 Chrome 或 Safari 查看。Firefox 计划在版本 109 中发布支持

案例 1:卡片网格

您可能已经想到了这一点,对吧?这是一个如此常见的模式,以至于我们所有人似乎都会在某个时候遇到它。但事实是,如果我能够使用容器大小查询而不是标准媒体查询,那么容器大小查询将为我节省大量时间,并获得更好的结果。

假设您被要求构建这个卡片网格,要求是每个卡片需要保持 1:1 的纵横比

A four-by-three grid of card elements as a grayscale mockup.

它比看起来难得多!问题是,根据视窗宽度来调整组件内容的大小,会让您受制于组件对视窗的响应方式 — 以及任何其他祖先容器对它的响应方式。例如,如果您希望卡片标题的字体大小在卡片达到特定内联大小时减小,则没有可靠的方法可以做到这一点。

我想您可以使用 vw 单位设置字体大小,但组件仍然与浏览器的视窗宽度绑定。当卡片网格在可能没有相同断点的其他上下文中使用时,这可能会导致问题。

在我的现实世界项目中,我最终使用了 JavaScript 方法,该方法将

  1. 侦听调整大小事件。
  2. 计算每张卡片的宽度。
  3. 根据每张卡片的宽度向其添加内联字体大小。
  4. 使用 em 单位对内部的所有内容进行样式化。

看起来工作量很大,对吧?但它是一个稳定的解决方案,可以使不同屏幕尺寸和不同上下文中的缩放效果保持一致。

容器查询会好得多,因为它们为我们提供了 **容器查询单位**,例如 cqw 单位。您可能已经知道了,1cqw 等于容器宽度的 1%。我们还有 cqi 单位,它是容器内联宽度的度量,以及 cqb,它是容器块宽度的度量。因此,如果我们有一个宽度为 500px 的卡片容器,50cqw 值将计算为 250px

如果我能够在我的卡片网格中使用容器查询,我可以将 .card 组件设置为一个容器

.card { 
  container: card / size;
}

然后我可以使用 cqw 单位设置一个内部包装器,该包装器具有 padding,其大小按 .card 宽度的 10% 进行缩放

.card__inner { 
  padding: 10cqw; 
} 

这是一个不错的方法,可以在任何视窗宽度下,无论卡片在何处使用,都能一致地调整卡片边缘与其内容之间的间距。无需使用媒体查询!

还有一个想法?对内部内容的字体大小使用 cqw 单位,然后使用 em 单位应用 padding

.card__inner { 
  font-size: 5cqw; 
  padding: 2em;
} 

5cqw 是一个任意值 — 我只是决定使用它。该 padding 仍然等于 10cqw,因为 em 单位是相对于 .card__inner 字体大小的!

您注意到这一点了吗?2em 相对于设置在 同一容器 上的 5cqw 字体大小!容器的工作方式与我们习惯的方式不同,因为 em 单位是相对于相同元素的 font-size value 的。但我很快注意到,容器查询单位与 最近的父容器(也是容器) 相关。

例如,在本例中,5cqw 不会根据 .card 元素的宽度进行缩放

.card { 
  container: card / size; 
  container-name: card; 
  font-size: 5cqw; 
}

相反,它会根据最近的定义为容器的父元素进行缩放。这就是为什么我设置了 .card__inner 包装器的原因。

案例 2:交替布局

我需要在另一个项目中使用另一个卡片组件。这次,我需要卡片从横向布局过渡到纵向布局...然后再次从横向过渡到纵向,随着屏幕变小。

Showing four states of a card element changing between portrait and landscape layouts at various breakpoints.

我做了很多工作来让这个组件在这两个特定的视窗范围内切换到纵向布局(感谢新的媒体查询范围语法!),但问题是,它仍然锁定在设置在它、它的父元素以及任何可能响应视窗宽度的东西上的媒体查询。我们想要的是在任何条件下都能正常工作的解决方案,无需担心内容将要断开!

容器查询可以轻松实现这一点,这得益于 @container 规则

.info-card {
  container-type: inline-size;
  container-name: info-card;
}

@container info-card (max-width: 500px) {
  .info-card__inner {
    flex-direction: column;
  }
}

一个查询,无限的流畅性

但是等等!您可能需要留意一些事情。具体来说,在基于属性的设计系统中使用这种容器查询可能会很困难。例如,此 .info-card 组件可能包含依赖属性来更改其外观的子组件。

为什么这很重要?卡片的纵向布局可能需要备用样式,但您无法使用 CSS 更改 JavaScript 属性。因此,您有可能会重复所需的样式。我实际上已经谈到了这一点,以及如何在另一篇文章中解决此问题。如果您需要对大量样式使用容器查询,那么您可能需要围绕容器查询来构建整个设计系统,而不是试图将它们强行塞入以媒体查询为主的设计系统中。

案例 3:SVG 描边

这是一个我最近使用的另一个非常常见的模式,容器大小查询可以带来更精致的产品。假设您有一个与标题锁定的图标

<h2>
  <svg>
    <!-- SVG stuff -->
  </svg> 
  Heading
</h2>

即使没有媒体查询,也相当容易根据标题的大小来缩放图标。但问题是,SVG 的 stroke-width 在较小的尺寸下可能会太细而难以察觉,而在较大的尺寸下可能会显得过粗而引人注目。

我不得不为每个图标实例创建和应用类,以确定其大小和描边宽度。如果图标位于旁边的是一个使用固定字体大小进行样式化的标题,那还算可以,但对于不断变化的流体文本来说,就不太好了。

A lockup of a hexagon icon and heading at three different sizes, from large to small.

标题的字体大小可能基于视窗宽度,因此 SVG 图标需要相应调整,使其描边在任何尺寸下都能正常工作。您可以通过在 em 单位中设置描边宽度,使其相对于标题的 font-size 进行缩放。但如果您需要使用一组特定的描边尺寸,那么这样做就不会起作用,因为它会线性缩放 — 除非使用视窗宽度的媒体查询,否则无法在某些点将其调整到特定的 stroke-width 值。

但如果我当时可以使用容器查询,那么我会这样做

.icon {
  container: icon / size; 
  width: 1em; 
  height: 1em; 
}

.icon svg {
  width: 100%; 
  height: 100%; 
  fill: none; 
  stroke: #ccc; 
  stroke-width: 0.8; 
}

@container icon (max-width: 70px) {
  .icon svg {
    stroke-width: 1.5; 
  }
}
@container icon (max-width: 35px) {
  .icon svg {
    stroke-width: 3;
  }
}

比较这些实现,看看容器查询版本是如何根据容器的宽度将 SVG 的描边捕捉到我想要的特定宽度上的。

奖励:其他类型的容器大小查询

好吧,我还没有在实际项目中遇到过这种情况。但当我浏览有关容器查询的信息时,我注意到我们还可以查询与容器大小或物理尺寸相关的容器的其他内容。

我所见过的多数示例都像我在本文中一直做的那样,查询 widthmax-widthmin-widthheightblock-sizeinline-size

@container info-card (max-width: 500px) {
  .info-card__inner {
    flex-direction: column;
  }
}

MDN 概述了我们可以查询的另外两个内容。一个是 orientation,这很合理,因为我们一直在媒体查询中使用它。容器查询也不例外

@media screen (orientation: landscape) { 
  .info-card__inner {
    /* Style away! */
  }
} 

@container info-card (orientation: landscape) { 
  .info-card__inner {
    /* Style away! */
  }
} 

另一个?令人难以置信的是,它是 aspect-ratio

@container info-card (aspect-ratio: 3/2) { 
  .info-card__inner {
    /* Style away! */
  }
} 

这是一个可编辑的演示,您可以用它来测试这两个示例

我还没有找到任何这两种查询的良好用例。如果您有任何想法或觉得它对您的项目有所帮助,请在评论区告诉我!