现代 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 模拟组件响应式,是时候重写一遍了。