姆姆极客分享MU·GEEK·SHARE
// CSS Container Queries @container sidebar .card (narrow) .card (wide) one component, multiple layouts by container size
前端开发·

现代 CSS 容器查询与子网格实战:真正组件化的布局时代

容器查询与 subgrid 已全面落地主流浏览器,本文从真实场景出发,演示如何用它们写出真正"与环境无关"的组件,并对比传统方案的差异。


前端做了这么多年“响应式”,其实一直在用一种别扭的方式——根据视口宽度决定组件长什么样。但组件真正关心的是“我被放在了多大的容器里”,而不是“用户的屏幕有多宽”。容器查询(Container Queries)把这个错位彻底修正了。

一、容器查询:从“视口响应”到“容器响应”

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

@container (min-width: 400px) {
  .card__body {
    display: grid;
    grid-template-columns: 120px 1fr;
    gap: 1.5rem;
  }
}

@container (max-width: 399px) {
  .card__body {
    display: flex;
    flex-direction: column;
  }
}

.card 声明为容器后,内部所有 @container 查询都基于它的 inline-size 判断。同一个组件放到侧边栏、放到主内容区、放进弹窗——同一份 CSS 自动适配。

对比传统的 @media (min-width: 768px),最大区别在于:组件不再“知道”自己被放在了页面什么位置。这才是组件化的真正含义。

container-type 的选择

  • inline-size:最常用,只基于行内尺寸(宽度)查询。
  • size:双向查询,但会让容器脱离自身内容计算 size,慎用。
  • normal:默认值,仅作为容器查询单元的祖先,不参与查询。

命名容器

复杂布局里会有多层容器嵌套,container-name 让你指定查询的是哪一层:

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

@container sidebar (min-width: 300px) {
  .card { /* 侧边栏足够宽时的样式 */ }
}

二、subgrid:让子网格继承父网格

subgrid 是另一个被等待了 5 年的特性。它解决了 grid 嵌套时“子网格无法对齐父网格轨道”的痛点。

.timeline {
  display: grid;
  grid-template-columns: 80px 1fr 1fr;
  gap: 1rem;
}

.timeline__item {
  display: grid;
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
}

.timeline__time    { grid-column: 1; }
.timeline__title   { grid-column: 2; }
.timeline__content { grid-column: 3; }

过去要实现这种“时间轴三列严格对齐”,要么硬写三套 grid,要么用 display: contents 把子项“穿透”到父网格——但穿透会丢失子项的语义边界。subgrid 是真正干净的解法。

实战:跨容器对齐表单标签

.form {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 0.8rem 1.5rem;
}

.form__row {
  display: grid;
  grid-template-columns: subgrid;
  grid-column: 1 / -1;
}

.form__row label { grid-column: 1; align-self: center; }
.form__row input { grid-column: 2; }

无论某一行 label 多长,所有 label 都会自动对齐到同一列起点。这种“对齐”在以前只能靠 display: contents 或预渲染结构实现。

三、容器查询单位:cqw / cqi / cqh

类似 vw / vh,但相对的是容器尺寸:

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

.card__title {
  font-size: clamp(1rem, 5cqi, 2rem);
}

5cqi = 容器 inline-size 的 5%。配合 clamp,标题字号会随容器宽度自适应,与视口完全无关

四、实战:一个真正“环境无关”的卡片组件

<article class="product-card">
  <img src="..." alt="..." />
  <div class="product-card__body">
    <h3>无线降噪耳机</h3>
    <p class="price">¥ 1,299</p>
    <button>加入购物车</button>
  </div>
</article>
.product-card {
  container-type: inline-size;
  container-name: card;
  display: flex;
  flex-direction: column;
  border: 1px solid #2a2f4a;
  border-radius: 8px;
  overflow: hidden;
}

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

.product-card__body {
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

@container card (min-width: 360px) {
  .product-card { flex-direction: row; }
  .product-card img { width: 40%; aspect-ratio: 1; }
  .product-card__body { flex: 1; }
}

@container card (min-width: 600px) {
  .product-card__body { flex-direction: row; align-items: center; justify-content: space-between; }
  .product-card button { align-self: center; }
}

这个组件放到 280px 侧栏、560px 主区、960px hero 区,都能自动调整布局——而 CSS 一份不动。

五、浏览器支持与渐进增强

截至 2026 年初,容器查询、subgrid、cqi 单位在 Chrome/Edge/Safari/Firefox 的近 3 个大版本中均稳定支持。Baseline 已经把它们标记为“widely available”。

如果你还在维护老项目,可以用 @supports 做降级:

.card__body { display: flex; flex-direction: column; }

@supports (container-type: inline-size) {
  .card { container-type: inline-size; }
  @container (min-width: 400px) {
    .card__body { flex-direction: row; }
  }
}

结语

容器查询和 subgrid 是 CSS 历史上少有的“真正改变了写组件的心智模型”的特性。它们让你可以写出与上下文解耦的组件,不再为“这个组件在哪些页面里被用了”操心。如果你还在用 @media 模拟组件响应式,是时候重写一遍了。