
使用 CSS 容器查询入门
从今年开始,所有主流浏览器都支持容器查询。但它们到底是什么,我们如何使用它们来构建更健壮、更灵活的布局?我们是否仍然需要媒体查询?让我们来一探究竟。
媒体查询用于布局的问题
为了了解容器查询如何提供帮助,让我们来看一个新闻提要布局的示例,并了解可以在哪里使用它们。新闻提要是一系列文章,每篇文章都包含一张图片、一个标题和一段文字摘要。页面的右侧还有一个边栏,列出了热门文章。
我们可以将它分为两个网格:左侧为 4 列网格,右侧为单列(边栏)。
在左侧,我们有一篇跨越四列的大型特色文章。在这篇文章下面,两篇文章分别跨越两列。它们采用水平布局,左侧为图片,右侧为文字。再下面是四篇较小的文章,每篇都跨越一列。右侧也有一个边栏,包含一列与之风格相同的文章。
我们可能会看到这个布局中有三种不同的文章样式,所有这些文章都将作为单独的组件进行开发。但让我们看看设计在更小的视窗中会发生什么变化。
在移动设备尺寸下,所有文章的图片都堆叠在文字之上,包括特色文章。它们的布局没有区别。在稍微大一点的尺寸下,文章将采用水平布局。在更大的视窗(我们可能会认为大约是平板电脑尺寸)上,特色文章下面有两篇文章采用水平布局,下面还有四篇堆叠的文章。总共有三种不同的文章布局。
如果我们使用媒体查询来查询视窗大小来编写此布局代码,则可能需要创建单独的组件来处理不同断点下每种不同文章布局的行为。我们的代码很容易变得笨重且难以维护。
将内容放入可用空间中
想象一下,一位编辑想要将不同的内容放置在我们新闻提要旁边。我们的 5 列布局在可用空间中不再好看,必须重新设计。在这种情况下,我们可以将一些文章垂直堆叠。
使用媒体查询来检测视窗的宽度,我们的布局无法响应可用空间的变化。如果我们可以查询父元素的宽度——或**容器**,那会好得多。
什么是容器查询?
容器查询允许我们查询元素的大小,而不是视窗的大小,并相应地设置其子元素的样式。我们可以像使用媒体查询一样使用它们,但它们在布局方面提供了更大的灵活性——并且有可能大大简化我们的代码。
使用容器查询构建组件布局
让我们以单个文章组件为例。我们将逐步介绍如何使用容器查询构建响应式文章组件布局。
创建容器
在编写容器查询之前,我们需要一个要查询的容器元素!在我们的新闻提要布局中,我们需要一个包裹每个文章的容器元素作为容器。由于我们的网格是文章列表,因此每个文章的容器元素将是<li>
。我们将为它添加一个名为article-container
的类,以避免混淆。
<ul class="grid">
<li class="article-container">
<article>...</article>
</li>
<li class="article-container">
<article>...</article>
</li>
<li class="article-container">
<article>...</article>
</li>
...
</ul>
我们使用两个 CSS 属性创建容器:container-name
和container-type
。container-name
是可选的,但如果我们在页面上有多个容器,它会很有用,因为它可以明确地告诉我们正在引用哪个容器。容器名称可以是任何我们喜欢的名称。我们将这个容器命名为“article”。
对于基于大小的容器查询,container-type
应为inline-size
。inline-size
是一个逻辑值,因此它指的是使用水平书写模式(默认值)时的宽度,或者指的是使用垂直书写模式时的高度。size
是另一个可能的值,但它指的是CSS 包含模块,正如 Stephanie Eckles 在她在 Smashing Magazine 上发表的文章中解释的那样,它与我们这里无关。
.article-container {
container-name: article;
container-type: inline-size;
}
在撰写本文时,无法查询块大小。
简写
简写container
属性允许我们同时设置容器名称和容器类型(此属性需要容器名称)。
.article-container {
container: article / inline-size;
}
查询容器
定义了容器后,编写容器查询类似于编写媒体查询。在编写查询时,我们可以选择指定一个命名容器,也可以省略它,在这种情况下,查询将默认使用最近的容器。如果我们没有定义容器,那么我们的容器查询的行为将非常类似于媒体查询——也就是说,它将查询视窗大小。
我们可能会发现指定一个命名容器很有帮助,这样我们就可以确定正在查询哪个容器。这对于可能有多个嵌套容器的布局尤其有用。
/* Without a named container */
@container (width > 700px) {
/* Styles applied to elements within any container when the container is over 700px wide */
}
/* Specifying a named container */
@container article (width > 700px) {
/* Styles applied to elements within the 'article' container when the container is over 700px wide */
}
就像媒体查询一样,我们可以使用任意数量的容器查询。
.article-container {
container: article / inline-size;
}
article {
display: grid;
gap: 1rem;
}
@container article (inline-size > 500px) {
/* Styles for horizontal article */
article {
grid-template-columns: 1fr 2fr;
}
}
@container article (inline-size > 800px) {
article {
/* Styles for article in a large space (e.g. featured article) */
grid-template-columns: 1fr 1fr;
gap: 2rem;
font-size: 1.2rem;
}
h3 {
font-size: 2rem;
}
}
新的媒体查询语法
您可能会注意到,我们编写的这些容器查询与传统的媒体查询相比,在写法上略有不同。我们没有使用min-width
和max-width
,而是使用了媒体查询范围语法。这样做的好处是,它可以让我们的查询更加简洁。
/* Old syntax for styles between 700px and 900px */
@container (min-width: 700px) and (max-width: 900px) {
}
/* New syntax */
@container (700px <= width <= 900px) {
}
它也更有利于使用逻辑属性。我们不必编写依赖于最小宽度或最大宽度的样式,而是可以这样写:“如果内联大小**大于**(或**小于**)x,则应用这些样式”。
我们之前的容器查询示例可以改写为使用inline-size
而不是min-width
。
@container article (inline-size > 700px) {
article {
/* Styles for horizontal article */
}
}
嵌套容器
除了响应式文章组件之外,我们的网格布局本身也需要响应可用空间。
这意味着我们需要在网格周围创建一个额外的容器。我们需要调整我们的标记以包含此额外的包装元素。
<div class="grid-container">
<ul class="grid">
<li class="article-container">...</li>
<li class="article-container">...</li>
<li class="article-container">...</li>
...
</ul>
</div>
.grid-container {
container: layout / inline-size;
}
我们将编写容器查询,分别为我们的网格指定两列布局和四列布局,只要有足够的可用空间。
/* Initial styles for single column layout */
.grid {
display: grid;
gap: 1rem;
}
@container layout (inline-size > 800px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
/* The featured article should span two columns */
.article-container:first-child {
grid-column: span 2;
}
}
@container layout (inline-size > 1000px) {
.grid {
grid-template-columns: repeat(4, 1fr);
}
.article-container:first-child {
grid-column: span 4;
}
.article-container:nth-child(2),
.article-container:nth-child(3) {
grid-column: span 2;
}
}
如果我们调整组件的大小,我们可以看到,我们的网格布局和嵌套的文章都响应了可用空间。
容器单位
您可能已经熟悉视窗单位,可以使用它们相对于视窗的大小设置元素的大小。容器单位使我们能够相对于容器的大小设置元素的大小。
容器单位对于设置文章组件的样式很有用。我们可以使用它们来指定填充和字体大小,从而减少对不同断点进行调整的必要性。
单位cqi
用于指定容器内联大小的百分比。
article {
padding: 4cqi;
}
(我们不需要在容器查询中编写这些样式。只要定义了容器,它们就会响应容器)。
为了防止填充变得过大或过小,我们可以使用clamp()
函数。它将解析为中间值,因此,通过在灵活的cqi
值之外再传递两个固定值,我们可以确保填充永远不会小于1rem
或大于3rem
。
article {
padding: clamp(1rem, 4cqi, 2rem);
}
类似地,我们可以使用clamp
函数和容器单位设置字体大小。
浏览器支持和回退
截至 2023 年,所有主流浏览器都支持容器查询。但是我们应该知道,并非所有用户都使用最新版本的浏览器。如果您需要满足使用旧版浏览器的用户需求,Chrome 团队提供了一个易于使用的polyfill。
样式查询
到目前为止,我们已经介绍了如何使用容器查询来查询元素的内联大小以构建布局。但容器查询还可以应用于更多方面。我们还可以查询元素使用自定义属性应用的样式。
在我们的示例布局中,我们可能想要使用不同的样式来突出显示“特色”文章。在标记中设置一个自定义属性后,我们就可以对具有特定自定义属性值的元素应用样式。
<div class="grid-container">
<ul class="grid">
<li class="article-container">...</li>
<li class="article-container">...</li>
<li class="article-container" style="--featured: true">...</li>
...
</ul>
</div>
@container style(--featured: true) {
article {
border-radius: 0.2rem;
background: pink;
border: 1px solid deeppink;
padding: clamp(1rem, 5cqi, 3rem);
}
}
我们不需要定义容器,因为每个元素都自动具有样式包含。阅读Una Kravets 的文章,了解有关样式容器查询的更多信息。
限制和浏览器支持
在撰写本文时,Chrome 和 Edge 支持容器样式查询。它们目前在 Safari 或 Firefox 中不受支持。在CSS 包含模块第 3 级规范中,任何属性值对都可以被查询,而不仅仅是自定义属性。但是,目前还没有浏览器支持这项功能。
我们是否仍然需要媒体查询?
如果我们能够使用容器查询来查询视窗大小和元素大小,我们是否仍然需要媒体查询?当然!媒体查询不仅仅用于查询大小。我们可以使用prefers-reduced-motion
和prefers-color-scheme
来检测用户的偏好,仅举两例。
我们仍然可能会遇到需要根据视窗大小来设置嵌套容器中元素的样式的情况,而媒体查询可能是最简单的方法。但我预测,在不久的将来,容器查询将在很大程度上取代媒体查询在布局方面的应用,而媒体查询将主要保留用于上述其他媒体特征。
总结
容器查询使我们能够更好地控制响应式布局,并帮助我们编写更简洁、更健壮、更易维护的 CSS。现在,容器查询得到了广泛的浏览器支持,现在是尝试它的好时机。最棒的是,你不必立即全部使用它:如果你正在使用媒体查询构建某个组件遇到困难,那么值得尝试容器查询,它可能是你正在寻找的答案。
查看此处整合了本文中所有示例的完整演示
其他资源
- CSS Containment 模块级别 3
- 视频:CSS 容器,它们知道什么? 由 Miriam Suzanne 在 CSS Day 上发布
- 开始使用样式查询 由 Una Kravets 发布
- 新的 CSS 媒体查询范围语法 由 Preethi Selvam 发布