Getting started with CSS container queries title. A vibrant gradient behind artwork representing a web browser and a mechanical keyboard.

CSS 容器查询入门

阅读时间 8 分钟

截至今年,所有主流浏览器都支持容器查询。但它们是什么,我们如何使用它们来构建更健壮、更灵活的布局?我们还需要媒体查询吗?让我们一探究竟。

关于媒体查询用于布局的问题

为了理解容器查询的便利性,我们来看一个新闻信息流布局的例子,看看在哪里可以应用它们。新闻信息流是文章的集合,每篇文章都包含图片、标题和文字摘要。页面右侧还有一个侧边栏,列出热门文章。

News feed layout shown on desktop

我们可以将它分成两个网格:左侧一个4列网格,右侧一个单列(侧边栏)。

The news feed grid layout

在左侧,有一篇横跨四列的大型特色文章。在此下方,两篇文章各横跨两列。它们采用水平布局,图片在左,文本在右。在此下方是四篇较小的文章,每篇横跨一列。一列相同样式的文章也出现在右侧作为侧边栏。

我们可以查看此布局,发现三种不同的文章样式,所有这些都可以作为单独的组件来开发。但让我们看看在较小的视口上设计会发生什么。

Mobile and tablet versions of the news feed layout

在移动设备尺寸下,所有文章(包括特色文章)的图片都堆叠在文本上方。它们的布局无法区分。在稍大的尺寸下,文章采用水平布局。在更大的视口(我们可能认为是接近平板电脑尺寸的)上,特色文章下方有两篇水平文章,下方有四篇堆叠的文章。总共有三种不同的单个文章布局。

News feed layout shown on desktop

如果我们要使用媒体查询来查询视口大小来编写此布局,我们可能需要创建单独的组件来处理各种断点下不同文章布局的行为。我们的代码很容易变得有点笨重,并且难以维护。

将内容适配到可用空间

想象一下,一位编辑过来,想在我们的新闻信息流旁边放置不同的内容。我们原来的5列布局在可用空间中不再好看,必须重新设计。在这种情况下,我们可以将一些文章垂直堆叠。

Our layout adjusted to accommodate additional content alongside

使用媒体查询来检测视口宽度,我们的布局无法响应可用空间的变化。如果我们能查询父元素(或**容器**)的宽度,那就好多了。

什么是容器查询?

容器查询允许我们查询元素的大小,而不是视口的大小,并相应地设置其后代元素的样式。我们可以以类似媒体查询的方式使用它们,但它们为我们提供了更大的布局灵活性,并有可能大大简化我们的代码。

使用容器查询构建组件布局

以单个文章组件为例。我们将逐步介绍如何使用容器查询来构建响应式文章组件布局。

Article component layout at three different sizes

创建容器

在编写容器查询之前,我们需要一个要查询的容器元素!在我们的新闻信息流布局中,我们需要一个包装每篇文章的容器元素来作为容器。由于我们的网格是文章列表,每篇文章的容器元素将是<li>。我们会给它一个类名article-container,以避免任何混淆。

The elements of our container query

html
<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-namecontainer-typecontainer-name 是可选的,但如果有多个容器在页面上,它会很有用,因为它让我们清楚地知道我们在引用哪个容器。容器名称可以是任何我们想要的。我们将这个命名为“article”。

对于基于大小的容器查询,container-type 应为 inline-sizeinline-size 是一个逻辑值,因此在水平书写模式(默认)下,它指的是宽度,而在垂直书写模式下,它指的是高度。size 是另一个可能的值,但它指的是CSS containment 模块,正如 Stephanie Eckles 在她的 Smashing Magazine 文章中所解释的那样,它与我们这里无关。

css
.article-container {
  container-name: article;
  container-type: inline-size;
}

在撰写本文时,无法查询块尺寸。

简写

简写属性 container 允许我们同时设置容器名称和容器类型(此属性需要容器名称)。

css
.article-container {
  container: article / inline-size;
}

查询容器

定义了容器后,编写容器查询与编写媒体查询类似。编写查询时,我们可以选择指定一个命名容器,也可以省略它,在这种情况下,查询将默认使用最近的容器。如果我们没有定义容器,那么我们的容器查询将非常像媒体查询——也就是说,它将查询视口大小。

指定一个命名容器可能很有帮助,这样我们可以确切知道正在查询哪个容器。这对于我们可能有多个嵌套容器的布局尤其有用。

css
/* 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  */
}

与媒体查询一样,我们可以使用任意数量的容器查询。

css
.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-widthmax-width。这使得我们的查询更简洁。

css
/* 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

css
@container article (inline-size > 700px) {
  article {
    /* Styles for horizontal article */
  }
}

嵌套容器

除了响应式文章组件外,我们自己的网格布局也需要响应可用空间。

Layout grid at three different sizes

这意味着我们需要在网格周围创建额外的容器。我们需要调整标记以包含这个额外的包装元素。

html
<div class="grid-container">
  <ul class="grid">
    <li class="article-container">...</li>
    <li class="article-container">...</li>
    <li class="article-container">...</li>
    ...
  </ul>
</div>
css
.grid-container {
  container: layout / inline-size;
}

我们将编写容器查询,在空间足够时分别为我们的网格指定两列布局和四列布局。

css
/* 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 单位用于指定容器内联尺寸的百分比。

css
article {
  padding: 4cqi;
}

(我们不需要在容器查询中编写这些样式。只要定义了容器,它们就会响应容器。)

为了防止内边距变得过大或过小,我们可以使用 clamp() 函数。它解析为中间值,因此通过传递两个固定值以及我们的灵活 cqi 值,我们可以确保内边距永远不会小于 1rem 或大于 3rem

css
article {
  padding: clamp(1rem, 4cqi, 2rem);
}

同样,我们可以使用 clamp 函数和容器单位来设置字体大小。

浏览器支持与回退

截至 2023 年,所有主要浏览器都支持容器查询。然而,我们应该意识到并非所有用户都拥有最新的浏览器。如果您需要支持旧版浏览器的用户,有一个易于使用的 Chrome 团队的 polyfill

样式查询

到目前为止,我们已经介绍了如何使用容器查询来查询元素的内联尺寸来构建布局。但容器查询的应用远不止于此。我们还可以查询带有自定义属性的元素的样式。

在我们的示例布局中,我们可能希望通过不同的样式来吸引“特色”文章的注意力。在我们的标记中设置自定义属性,然后我们可以为具有特定自定义属性值的元素应用样式。

html
<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>
css
@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 Containment Module Level 3 规范中,为查询任何属性-值对(不仅仅是自定义属性)提供了支持。但目前还没有浏览器支持这一点。

我们还需要媒体查询吗?

如果我们能够使用容器查询来查询视口大小元素大小,我们是否还需要媒体查询?当然需要!媒体查询用于查询的不仅仅是大小。我们可以检测用户的偏好,例如 prefers-reduced-motionprefers-color-scheme,仅举两例。

仍然可能出现需要根据视口大小为嵌套容器内的元素设置样式的情况,而媒体查询可能是最简单的方法。但我预测,在不久的将来,容器查询将在布局方面在很大程度上取代媒体查询,而媒体查询将主要保留用于上述等其他媒体特性。

总结

容器查询为我们提供了对响应式布局的更多控制,同时也帮助我们编写更简洁、更健壮、更易于维护的 CSS。既然它们已经获得了广泛的浏览器支持,现在是尝试它们的好时机。最棒的是,您不必立即全部投入:如果有一个组件您在使用媒体查询时难以构建,那么值得看看容器查询,因为它们可能是您正在寻找的答案。

在此处查看结合本文所有示例的完整演示

其他资源