使用容器查询(Container Query)打造真正的响应式设计

profile image

了解如何使用容器查询来解决媒体查询难以处理的响应式设计问题。通过根据父元素的尺寸而非视口尺寸来应用样式,让我们创建真正可复用的组件。

本帖由 Jetbrains's Coding Agent Junie junie logo翻译。如有任何翻译错误,请告知我们!

大多数开发者在第一次学习和创建响应式设计时,可能都是使用媒体查询(Media Query)来完成的。

在构建响应式网页时,使用媒体查询可以根据视口大小更改布局。然而,在开发过程中,经常会遇到设计需要根据组件实际占据的空间而不是视口来变化的情况。

例如,同一个组件可能被放置在宽敞的主内容区域,也可能被放置在狭窄的侧边栏区域。由于这两个区域占据的空间不同,无法应用相同的设计,在狭窄区域布局很可能会崩溃。媒体查询很难妥善处理这种情况。在这种情况下,使用容器查询可以创建能够响应父元素尺寸的、真正可复用的组件。

媒体查询的局限性

让我们更具体地看一下上面提到的情况。

媒体查询是如何工作的?

媒体查询根据视口的大小应用样式。

css
/* 当视口宽度大于等于 768px 时 */
@media (min-width: 768px) {
  .card {
    display: grid;
    grid-template-columns: 200px 1fr;
  }
}

这段代码的逻辑是“如果屏幕够宽,就横向显示卡片”。在以视口为中心的设计中,这看起来足够了,但在以组件为中心的设计中,问题就出现了。

实际遇到的情况

同一个组件在多个地方以不同的尺寸使用时

有一个订阅新闻邮件的组件。在桌面端(1280px)上,输入框和按钮横向排列,而当尺寸小于该值时,它们纵向排列。假设我们将此组件放置在桌面屏幕上的宽区域(800px)和窄区域(300px)中。

由于媒体查询只看视口,它会判断“因为大于 1280px,所以应该应用横向布局!”,结果在窄区域也应用了横向布局,导致组件布局崩溃。

브라우저 크기를 늘려보세요.

媒体查询的局限性可以总结如下:

  • 仅以视口为判断基准:无法知道组件实际占据的空间
  • 组件可复用性降低:将相同的组件放置在不同尺寸的区域时,其显示效果可能与预期不符
  • 对布局更改脆弱:如果父布局发生变化,子组件的样式也必须随之修改

这些问题可以通过容器查询来解决。

什么是容器查询?

与媒体查询不同,容器查询是一项 CSS 功能,它允许您根据父元素(容器)的尺寸而非视口来应用样式。通过这种方式,您可以创建根据组件实际占据的空间进行响应的、真正可复用的组件。

容器查询不仅可以根据尺寸,还可以根据以下容器状态来应用样式:

尺寸查询 (Size Queries)

  • 根据容器的宽度(width)、高度(height)应用样式
  • 最常用的方式

滚动状态查询 (Scroll State Queries)

  • 根据容器的滚动状态应用样式

简单示例

让我们将容器查询应用于我们最初提到的情况。

css
.card-container {
  container-type: inline-size;
}

/* 当容器小于 400px 时:纵向布局 */
.card {
  display: flex;
  flex-direction: column;
}

/* 当容器大于等于 400px 时:横向布局 */
@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 180px 1fr;
  }
}
css
<div class="layout">
  <main class="main-content">
    <!-- 主内容区域:宽容器 -->
    <div class="card-container">
      <div class="card">...</div>
    </div>
  </main>

  <aside class="sidebar">
    <!-- 侧边栏:窄容器 -->
    <div class="card-container">
      <div class="card">...</div>
    </div>
  </aside>
</div>

虽然是同一个组件,但由于是根据容器尺寸而非视口尺寸应用样式,因此在宽区域自然显示为横向布局,在窄区域自然显示为纵向布局。

Sidebar: OK

基本用法

要使用容器查询,首先必须使用 container-type 属性在作为基准的父元素上指定容器类型。

css
/* 关键字值 */
container-type: normal;
container-type: size;
container-type: inline-size;
container-type: scroll-state;

/* 两个值 */
container-type: size scroll-state;

/* 全局值 */
container-type: inherit;
container-type: initial;
container-type: revert;
container-type: revert-layer;
container-type: unset;
  • normal:默认值。无法进行尺寸查询,只能进行样式查询
  • size:基于行内(横向)和块(纵向)方向的尺寸。
  • inline-size:基于行内方向(通常为宽度)的尺寸。
  • scroll-state:基于滚动状态。

使用 @container 规则编写查询

指定容器后,可以使用 @container at-rule 定义容器查询。它使用类似于媒体查询中 @media 的语法。

css
/* 基本语法 */
@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 180px 1fr;
  }
}

/* 最大宽度 */
@container (max-width: 400px) {
  .card {
    flex-direction: column;
  }
}

/* 范围指定 */
@container (min-width: 400px) and (max-width: 800px) {
  .card {
    padding: 20px;
  }
}

指定容器名称

当多个容器嵌套时,可以使用 container-name 属性指定特定的容器。

css
/* 给容器命名 */
.card-container {
  container-type: inline-size;
  container-name: card;
}

.sidebar-container {
  container-type: inline-size;
  container-name: sidebar;
}

/* 针对特定容器进行查询 */
@container card (min-width: 400px) {
  .card-title {
    font-size: 1.5rem;
  }
}

@container sidebar (min-width: 300px) {
  .sidebar-content {
    padding: 16px;
  }
}

如果不指定容器名称,则查询将基于最近的父容器应用。

新单位

随着容器查询的出现,也出现了以容器尺寸为基准的新长度单位。就像视口单位(vw, vh)基于视口尺寸一样,容器查询单位基于容器的尺寸。

容器查询长度单位

基本单位

  • cqw:容器宽度的 1% (container query width)
  • cqh:容器高度的 1% (container query height)
  • cqi:容器行内方向尺寸的 1% (container query inline-size)
  • cqb:容器块方向尺寸的 1% (container query block-size)
  • cqmincqicqb 中的较小值
  • cqmaxcqicqb 中的较大值

在大多数情况下,在横排文本模式下,cqi 代表宽度,cqb 代表高度。

css
.card {
  /* 随着容器变大,内边距也随之增加 */
  padding: 2cqi;
  gap: 1cqi;
}

样式查询 (Style Queries)

容器查询不仅可以查询尺寸,还可以查询应用在容器上的样式值。使用样式查询,可以根据 CSS 自定义属性(CSS 变量)的值动态更改子元素的样式。

基本用法

样式查询使用 style() 函数来检查容器的样式值。

css
/* 在容器中定义 CSS 变量 */
.card-container {
  --theme: dark;
}

/* 根据容器的样式值进行查询 */
@container style(--theme: dark) {
  .card {
    background: #1a1a1a;
    color: #ffffff;
  }
}

@container style(--theme: light) {
  .card {
    background: #ffffff;
    color: #000000;
  }
}

结语

不仅是 JavaScript,CSS 每年也在增加新的功能。以前必须用 JavaScript 复杂实现的东西,现在仅靠 CSS 就能完成了。因此,我们需要继续关注 CSS 的新功能。

使用容器查询,您可以创建真正可复用的组件。由于同一个组件无论放在哪里都能根据该空间自动响应,因此非常方便。感觉媒体查询难以解决的问题都得到了完美的解决。

但是,CSS 很难像 JavaScript 那样使用 polyfill。因此,在实际应用到项目之前,务必检查浏览器的支持情况。幸运的是,主要浏览器都支持容器查询,但滚动查询、样式查询等仍处于实验阶段,因此最好检查目标浏览器,并在必要时逐步应用。


参考