CSS Container Queries:组件按父容器尺寸响应式而不是视口

媒体查询(@media)按视口尺寸响应式。但同一个组件可能放在不同宽度的
容器里(侧栏窄 / 主区宽 / 仪表盘卡片各种尺寸),媒体查询不知道
组件实际可用宽度。

Container Queries(容器查询)让组件按 父容器 尺寸响应式。
2023 后全 evergreen 浏览器支持,可以放心用。

1. 启用容器

.card-container {
  container-type: inline-size;
  /* 或者 size(同时观察宽 + 高) */

  container-name: card;  /* 可选,命名容器便于精确引用 */
}

container-type: inline-size 让浏览器观察这个元素的宽度变化,
开销小(不需要观察高度)。

2. 容器查询

@container card (min-width: 400px) {
  .card-title { font-size: 1.5rem; }
  .card-image { display: block; }
}

@container card (min-width: 600px) {
  .card { display: grid; grid-template-columns: 200px 1fr; }
}

@container card 引用前面命名为 "card" 的容器。
不写名字也行:@container (min-width: 400px) 用最近的祖先容器。

3. 完整例子:自适应卡片

<div class="grid">
  <div class="card-wrap">
    <article class="card">
      <img src="thumb.jpg" alt="">
      <div>
        <h3>标题</h3>
        <p>描述...</p>
      </div>
    </article>
  </div>
  <div class="card-wrap">
    <article class="card">...</article>
  </div>
</div>
.grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 16px;
}

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

.card {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.card img { width: 100%; aspect-ratio: 16 / 9; object-fit: cover; }

/* 容器够宽时改横向布局 */
@container (min-width: 360px) {
  .card { flex-direction: row; }
  .card img { width: 120px; aspect-ratio: 1; flex-shrink: 0; }
}

视口窄时卡片竖排;视口宽时容器宽度 > 360px,自动横排。
同一组件 + 同一 CSS 在不同上下文行为不同

4. 容器查询单位

CSS 出了 cqw / cqh / cqi / cqb 等单位,相对于容器尺寸:

.card-title {
  font-size: clamp(1rem, 4cqi, 2rem);
}

4cqi = 4% 容器 inline 尺寸。容器越宽字体越大,但限制在 1-2rem 之间。

5. style queries(实验性)

按容器的某个 CSS 自定义属性查询:

.theme-dark { --mode: dark; container-type: normal; }

@container style(--mode: dark) {
  .card { background: #1e1e1e; color: #fff; }
}

适合切主题不影响组件 HTML 结构。仍是实验阶段,部分浏览器支持。

6. 与媒体查询配合

媒体查询管页面布局(侧栏开关、导航形态),容器查询管组件内部。
分工清晰:

/* 媒体查询:响应视口 */
@media (max-width: 800px) {
  .layout { grid-template-columns: 1fr; }
  .sidebar { display: none; }
}

/* 容器查询:响应组件可用空间 */
@container card (min-width: 400px) {
  .card { ... }
}

7. 命名 vs 匿名容器

匿名(不写 container-name)查询最近的祖先 container:

.parent { container-type: inline-size; }

@container (min-width: 500px) {
  .child { ... }
}

命名让你跨层级精确引用:

.page { container: page / inline-size; }
.card { container: card / inline-size; }

@container page (min-width: 1000px) {
  /* 引用 page 容器 */
}
@container card (min-width: 400px) {
  /* 引用 card 容器 */
}

8. polyfill / 回退

老浏览器不支持时降级:

.card {
  /* 默认(窄屏 / 不支持时的样子) */
  flex-direction: column;
}

@container (min-width: 360px) {
  .card { flex-direction: row; }
}

/* 或者 @supports 兜底 */
@supports not (container-type: inline-size) {
  /* 不支持容器查询的浏览器用媒体查询近似 */
  @media (min-width: 600px) {
    .card { flex-direction: row; }
  }
}

9. 性能

container-type: inline-size 让浏览器为这个元素建立 containment context。
开销很小(不重排不重绘),但避免无脑给所有元素加。
通常每个独立组件根加一个就好。

container-type: size(同时观察宽 + 高)更贵些,因为元素的高度
通常由内容决定,会创建一个潜在的"无限循环"风险。

10. 实际收益

之前没容器查询时,常见 hack:

  • 给容器加 class(.card--wide / .card--narrow)→ 业务代码要知道布局
  • ResizeObserver + JS 控制 → 跨框架不一致 + 性能差
  • 多套 CSS 类按 prop 切换 → 难维护

容器查询是这些痛点的官方解。组件 truly self-contained。

11. 工具支持

Tailwind CSS v3.4+ 有 container queries plugin:

<div class="@container">
  <div class="@md:flex @lg:grid">...</div>
</div>

UI 库(shadcn / Mantine)渐渐采纳。

踩过的坑

  • 自己引用自己:.card { container-type: inline-size; } 然后
    @container (min-width: ...) .card { width: ... } —— 改 width 会
    触发容器尺寸变化 → 触发条件再判断 → 死循环。浏览器有保护但视觉上抖。
  • 父子嵌套容器名相同:第二个 container-name 覆盖第一个,意外查询。
    跨层 query 务必命名清楚。
  • height container query 慎用:必须 container-type: size 且容器有
    确定高度(不能完全由内容撑开)。
  • 把 container-type 加到 body 上 → 全局影响,性能可能下降。粒度
    控制在组件根元素。
精确评价 共 0 人评价
可复现性
可复现 · 0 不可复现 · 0
文风
文风流畅 · 0 文风晦涩 · 0
立场
支持 · 0 反对 · 0

登录后即可对本帖作出评价。

评论区 0 条 · 所有人可在此交流

登录后参与评论。

还没有评论,来说两句。