做前端久了你会发现,媒体查询(Media Queries)只能看视口宽度,组件一旦被塞进侧边栏、卡片或弹窗,布局就全乱了。CSS 容器查询(Container Queries)让组件根据自己容器的尺寸自适应,才是真正的组件级响应式布局。本文用可复制的实战代码,带你从概念一路打通到生产落地。
一、为什么媒体查询不够用
媒体查询的断点锚定在视口(viewport)。当同一个卡片组件同时出现在「主内容区 720px」「右侧栏 300px」「移动端 360px」三处时,它拿到的永远是同一份视口宽度,无法知道自己被放在了多宽的容器里。结果是:要么在窄栏里文字挤成一团,要么在宽栏里留白尴尬。
容器查询把判断维度从「视口」下沉到「最近的查询容器」——组件不再问「屏幕有多宽」,而是问「装我的盒子有多宽」。这正是设计系统、组件库一直在等的能力:一个组件写一次,放哪儿都好看。
二、核心概念:查询容器与 container-type
要用容器查询,必须先给某个祖先元素声明为「查询容器」。关键属性是 container-type,它决定了容器对外暴露哪些尺寸维度:
| 取值 | 含义 | 典型场景 |
|---|---|---|
size | 同时建立行/列(inline 与 block)尺寸上下文,容器自身尺寸不再由内容撑开 | 需要同时按宽和高查询 |
inline-size | 只建立行内(通常是宽度)尺寸上下文,最常用、性能最好 | 卡片、侧边栏组件按宽自适应 |
normal | 不建立查询容器(默认) | 无需查询时 |
绝大多数业务组件用 inline-size 就够了——我们只关心容器有多宽。注意:一旦设了 container-type: size,容器会脱离内容驱动高度,需要给它显式高度,否则容易塌成 0。
三、语法实战:从 @container 开始
声明容器后,子元素就能用 @container 写查询。下面给父盒子一个名字,方便在复杂嵌套里精确定位:
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* 当 card 容器宽度 ≥ 480px 时,切换为横向布局 */
@container card (min-width: 480px) {
.card {
flex-direction: row;
}
.card__media {
width: 200px;
}
}
如果没写 container-name,@container (min-width: 480px) 会就近匹配第一个查询容器。命名容器在组件库里强烈推荐——避免误命中外层不相关的容器。
四、实战案例 1:可复用的自适应卡片
一个卡片组件,放进窄栏是纵向堆叠、放进宽栏是左图右文。HTML 结构如下:
<article class="card">
<img class="card__media" src="/thumb.jpg" alt="封面">
<div class="card__body">
<h3 class="card__title">标题</h3>
<p class="card__desc">摘要文字……</p>
</div>
</article>
CSS 用容器查询驱动布局与排版,组件自身完全不知道自己会被放在哪里:
.card {
display: flex;
flex-direction: column;
gap: 12px;
container-type: inline-size;
}
.card__media {
width: 100%;
border-radius: 12px;
aspect-ratio: 16 / 9;
object-fit: cover;
}
@container (min-width: 420px) {
.card {
flex-direction: row;
align-items: center;
}
.card__media {
width: 40%;
aspect-ratio: 4 / 3;
}
.card__title {
font-size: 1.4rem;
}
}
把这段放进设计系统的 .card 组件,无论是博客列表、商品橱窗还是后台 widget,都能自动适配所在栏宽,不用再为每个页面写一套媒体查询。
五、实战案例 2:同一组件在侧栏与主区的差异
真实页面里,主内容区宽、侧栏窄。用容器查询,同一个组件无需任何 JS,就能在两处呈现不同密度:
/* 主区:宽容器,双列信息 + 大图 */
@container (min-width: 640px) {
.teaser { grid-template-columns: 220px 1fr; }
.teaser__meta { display: flex; gap: 16px; }
}
/* 侧栏:窄容器,紧凑单列 */
@container (max-width: 360px) {
.teaser { grid-template-columns: 1fr; }
.teaser__meta { flex-direction: column; gap: 4px; }
.teaser__title { font-size: 0.95rem; }
}
注意 min-width 与 max-width 可混用,配合 and 还能写区间,例如 @container (min-width: 420px) and (max-width: 720px)。这种「组件自己决定长相」的模式,正是与媒体查询最大的区别。
六、容器查询单位 cqw / cqh / cqi
除了 @container 条件,容器查询还带来一组专属单位,按容器尺寸计算,比 vw/vh 更贴合组件:
.card__title {
/* cqi = 容器行内尺寸的 1%;cqw = 容器宽度的 1% */
font-size: clamp(1rem, 6cqi, 1.75rem);
}
.card__desc {
/* cqh = 容器高度的 1%(需 container-type: size) */
line-height: 1.6;
max-height: 40cqh;
overflow: hidden;
}
clamp() 配合 cqi 是流式排版的黄金组合:标题字号随卡片宽度平滑缩放,既不溢出也不过小。需要按高度缩放时才用 cqh,并记得容器要设 container-type: size。
七、容器查询 vs 媒体查询:怎么分工
| 维度 | 媒体查询 Media Queries | 容器查询 Container Queries |
|---|---|---|
| 判断依据 | 视口 / 设备宽度 | 组件所在容器宽度 |
| 适用层级 | 页面级整体布局 | 组件 / 区块级局部布局 |
| 复用性 | 换页面常要重写断点 | 组件一次写好,处处复用 |
| 典型用途 | 导航栏折叠、整页栅格 | 卡片、列表项、widget 自适应 |
结论很清晰:页面骨架用媒体查询,组件内部用容器查询。两者不是替代关系,而是互补。导航在 768px 收起用媒体查询;而导航里的单个菜单项如何在窄屏变紧凑,就交给容器查询。
八、浏览器兼容与渐进增强
容器查询已在所有现代浏览器(Chrome/Edge/Firefox/Safari 16+)稳定支持。对老浏览器的渐进增强策略很简单:先写一套「窄屏默认布局」作为基线,再用 @container 增强宽容器表现。这样不支持的浏览器拿到的是可用的默认布局,支持的浏览器获得更精致的适配,不会白屏。
/* 基线:所有浏览器都能用的纵向卡片 */
.card { display: flex; flex-direction: column; }
/* 渐进增强:支持的浏览器在宽容器里横向排列 */
@supports (container-type: inline-size) {
.card { container-type: inline-size; }
@container (min-width: 420px) {
.card { flex-direction: row; }
}
}
配合 HTTP/3 与 QUIC 提速部署 与合理的 前端缓存策略,组件级响应式能进一步降低首屏抖动、提升渲染稳定性。
九、在 Vue3 组件里落地
在组件化框架里,容器查询的价值被放大:一个 .vue 单文件组件自带作用域样式,把 container-type 写在组件根节点,@container 写在子节点,组件就能跨项目复用且互不干扰。用法与纯 CSS 完全一致,无需任何运行时依赖:
<!-- Card.vue -->
<template>
<article class="card">
<img class="card__media" :src="cover" />
<div class="card__body">
<h3>{{ title }}</h3>
<p>{{ desc }}</p>
</div>
</article>
</template>
<style scoped>
.card { container-type: inline-size; display: flex; flex-direction: column; }
@container (min-width: 420px) { .card { flex-direction: row; } }
</style>
关于 Vue3 的组合式 API 与 Pinia 状态管理,可参考 Vue3 组合式 API + Pinia 实战,把容器查询组件接入你的状态流。构建侧若想提速,也可复用前文的多阶段构建思路。
十、常见坑与最佳实践
1)别在容器自身上查询自己:container-type 要设在祖先,查询写在后代,否则尺寸上下文会自相矛盾。
2)size 类型慎用:它会让容器高度脱离内容,记得给显式高度。
3)命名容器防误命中:组件库里用 container-name 隔离作用域。
4)基线优先:先保证无容器查询时的可用布局,再增强。
5)控制嵌套深度:查询容器过多会增加布局计算,列表项级别即可,不必逐层包裹。
容器查询把「响应式」的重心从页面交还给组件,是现代 CSS 最值得投入的特性之一。把它和媒体查询分层使用,你的设计系统会第一次真正具备「放哪儿都好看」的能力。




