长期以来,前端开发者依赖 媒体查询(Media Queries)实现响应式布局,但媒体查询基于视口(viewport)尺寸做判断,无法感知组件自身的容器大小。当同一个组件被放置在不同宽度的侧边栏、主内容区或弹窗中时,仅靠媒体查询难以让组件自适应其父容器。
CSS 容器查询(CSS Container Queries)正是为了解决这一问题而诞生的。它允许组件根据自身所在容器的尺寸变化来调整样式,真正实现"组件级响应式设计"。
什么是容器查询
容器查询是一种基于 祖先容器 的尺寸(或样式特征)来应用不同样式的 CSS 条件规则。其核心思想是:
组件不关心整个页面的宽度,只关心自己被放在了多大的空间里。
浏览器支持情况:Chrome 105+、Safari 16+、Firefox 110+ 已全面支持,主流浏览器覆盖率已超过 90%。
基础语法
1. 声明容器
要让一个元素成为"容器",需要使用 container-type 和 container-name 属性:
.card-wrapper {
container-type: inline-size;
container-name: card-container;
}也可以使用简写形式 container:
.card-wrapper {
container: card-container / inline-size;
}2. container-type 的取值
| 值 | 说明 |
|---|---|
| inline-size | 对容器的行内轴(宽度)建立查询 |
| size | 对容器的宽度和高度都建立查询 |
| normal | 默认值,不建立查询 |
实际开发中,inline-size 是最常用的值,因为宽度通常是布局的主要变量。使用 size 需要格外小心,如果容器的高度由内容决定,容易导致死循环。
3. 编写容器查询
使用 @container 规则根据容器尺寸应用样式:
@container card-container (min-width: 400px) {
.card {
display: grid;
grid-template-columns: 200px 1fr;
}
.card-title {
font-size: 1.5rem;
}
}4. 省略容器名称
如果不指定 container-name,@container 会沿 DOM 树向上查找最近的容器祖先:
.card-wrapper {
container-type: inline-size;
}
/* 自动匹配最近的容器 */
@container (min-width: 400px) {
.card-content {
flex-direction: row;
}
}容器查询单位
容器查询引入了一组新的 CSS 单位,类似于视口单位(vw、vh),但基于容器的尺寸:
| 单位 | 说明 |
|---|---|
| cqw | 容器宽度的 1% |
| cqh | 容器高度的 1% |
| cqi | 容器行内尺寸的 1% |
| cqb | 容器块尺寸的 1% |
| cqmin | cqi 和 cqb 中较小值的 1% |
| cqmax | cqi 和 cqb 中较大值的 1% |
@container (min-width: 400px) {
.card-image {
width: 30cqw; /* 容器宽度的 30% */
height: 20cqh; /* 容器高度的 20% */
font-size: 3cqi; /* 字体随容器缩放 */
}
}容器查询 vs 媒体查询
| 对比维度 | 媒体查询 @media | 容器查询 @container |
|---|---|---|
| 查询依据 | 视口尺寸 | 祖先容器尺寸 |
| 作用范围 | 全局页面级 | 组件级 |
| 可复用性 | 组件在不同位置可能表现不一致 | 组件在任何容器中都自适应 |
| 设计理念 | 页面驱动 | 组件驱动 |
| 适用场景 | 整体布局切换(如移动端/桌面端) | 可复用组件的内部响应式 |
关键结论:容器查询不会取代媒体查询,二者是互补关系。页面级布局仍适合媒体查询,组件内部自适应则用容器查询。
实战案例
案例 1:自适应卡片组件
<div class="card-wrapper">
<div class="card">
<img class="card-image" src="photo.jpg" alt="封面">
<div class="card-body">
<h3 class="card-title">文章标题</h3>
<p class="card-desc">文章摘要内容,在宽容器中展示更多文字...</p>
<button class="card-btn">阅读更多</button>
</div>
</div>
</div>.card-wrapper {
container-type: inline-size;
container-name: card;
}
.card {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 1rem;
border-radius: 12px;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.card-image {
width: 100%;
aspect-ratio: 16 / 9;
object-fit: cover;
border-radius: 8px;
}
.card-desc {
display: none; /* 窄容器下隐藏摘要 */
}
/* 容器宽度 >= 400px 时切换为横向布局 */
@container card (min-width: 400px) {
.card {
flex-direction: row;
align-items: center;
}
.card-image {
width: 200px;
aspect-ratio: auto;
height: 150px;
flex-shrink: 0;
}
.card-desc {
display: block; /* 显示摘要 */
color: #666;
font-size: 0.9rem;
line-height: 1.6;
}
}
/* 容器宽度 >= 600px 时进一步放大 */
@container card (min-width: 600px) {
.card {
padding: 1.5rem;
}
.card-image {
width: 280px;
height: 200px;
}
.card-title {
font-size: 1.5rem;
}
}同一个 .card-wrapper 放在 300px 的侧边栏和 700px 的主内容区中,会自动呈现不同的布局,无需任何 JavaScript 干预。
案例 2:响应式导航栏
.nav-wrapper {
container-type: inline-size;
container-name: nav;
}
.nav {
display: flex;
align-items: center;
gap: 1rem;
}
.nav-links {
display: none;
}
.nav-toggle {
display: block;
}
@container nav (min-width: 500px) {
.nav-links {
display: flex;
gap: 1.5rem;
}
.nav-toggle {
display: none;
}
}
@container nav (min-width: 800px) {
.nav-links {
gap: 2rem;
font-size: 1.1rem;
}
}这个导航栏组件可以放在页面顶部(宽容器),也可以嵌入侧边面板(窄容器),自动切换展示模式。
容器查询的样式查询
容器查询的样式查询
除了尺寸查询,CSS 还支持 样式查询(Style Queries),允许根据容器上设置的 CSS 自定义属性值来应用样式:
.card-wrapper {
container-type: inline-size;
container-name: card;
}
/* 通过自定义属性控制主题变体 */
.card-wrapper[data-theme="dark"] {
--card-theme: dark;
}
@container card style(--card-theme: dark) {
.card {
background: #1a1a2e;
color: #e0e0e0;
}
}样式查询目前的支持度还在推进中,建议在生产环境中结合尺寸查询谨慎使用。
最佳实践
1. 容器粒度选择
将 container-type 声明在 组件的直接父包裹元素 上,而不是过高的祖先节点。容器层级越精确,查询越可控。
/* 推荐:精确控制 */
.sidebar .card-wrapper { container-type: inline-size; }
/* 不推荐:容器层级过高,影响范围不可预测 */
.page-content { container-type: inline-size; }2. 合理命名容器
使用有语义的 container-name 避免冲突:
/* 推荐 */
container-name: product-card;
container-name: nav-bar;
/* 不推荐 */
container-name: c1;
container-name: wrapper;3. 渐进增强策略
为不支持容器查询的浏览器提供降级方案:
/* 默认样式(降级方案) */
.card {
display: flex;
flex-direction: column;
}
/* 容器查询(渐进增强) */
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}
}也可以结合媒体查询作为后备:
/* 媒体查询作为后备 */
@media (min-width: 768px) {
.card {
flex-direction: row;
}
}
/* 容器查询优先(浏览器支持时覆盖) */
@container (min-width: 400px) {
.card {
flex-direction: row;
}
}4. 避免过度嵌套
容器查询支持多层嵌套,但嵌套过深会增加维护成本。建议控制在 2-3 层以内。
5. 性能考量
容器查询的匹配在布局阶段完成,浏览器对其做了优化。但需注意:
- 避免在大量同级元素上同时声明容器
container-type: size比inline-size开销更大- 结合
will-change提示浏览器优化频繁变化的容器
与现代 CSS 的协作
容器查询可以与以下 CSS 特性无缝配合:
- CSS Grid / Flexbox:在容器查询内切换布局模式
- CSS Nesting:在嵌套语法中直接使用
@container - :has() 选择器:结合父元素状态实现更精确的查询
- @layer:在 Cascade Layer 中组织容器查询的优先级
@layer components {
.card-wrapper {
container-type: inline-size;
container-name: card;
}
@container card (min-width: 400px) {
.card { flex-direction: row; }
}
}常见问题
不会。媒体查询适合页面级别的响应式(如整体布局从单列切换到多列),容器查询适合组件级别。两者互补使用效果最佳。
size 同时监听宽高变化,如果容器高度由内容决定(height: auto),内容变化会影响容器高度,容器高度变化又触发查询,可能导致循环。inline-size 只监听宽度,更加安全。
可以。通过 @container style() 语法可以查询容器上的 CSS 自定义属性,但样式查询的浏览器支持度目前不如尺寸查询广泛。
在 Chrome DevTools 中,可以检查元素时看到容器查询的匹配状态。此外,CSS.containerQuery API 允许 JavaScript 监听容器尺寸变化。
总结
CSS 容器查询是响应式设计领域最重要的演进之一。它将响应式的关注点从视口级别下沉到组件级别,让可复用组件真正做到"放到哪里都好看"。
核心要点回顾:
- 用
container-type: inline-size声明容器 - 用
@container编写条件样式 - 用
cqw / cqh等容器单位实现弹性尺寸 - 结合媒体查询做渐进增强
- 合理命名、控制嵌套层级、注意性能
随着浏览器支持的完善,容器查询已经成为现代前端项目中不可或缺的工具。如果你正在构建组件库或设计系统,现在是引入容器查询的最佳时机。