CSS Container Queries 完全指南:比媒体查询更强大的响应式方案
前言:媒体查询的局限性
在过去十多年里,@media 媒体查询一直是响应式设计的基石。我们通过检测视口宽度来决定布局如何变化:
/* 传统媒体查询 —— 基于视口 */@media (max-width: 768px) { .card { flex-direction: column; }}这在页面级布局中运作良好,但当我们进入组件化开发时代,问题暴露无遗:
- 组件不知道自己被放在哪里。 一个
.card组件可能出现在全宽的主内容区,也可能出现在 300px 的侧边栏里。视口宽度相同,但组件的可用空间截然不同。 - 样式与容器耦合。 你不得不为「侧边栏中的卡片」和「主区域中的卡片」写不同的选择器,破坏了组件的封装性。
- 断点爆炸。 随着布局嵌套层级增加,你需要越来越多的媒体查询组合来覆盖各种场景。
CSS Container Queries 的出现,彻底解决了这个问题——让组件根据自身容器的尺寸来响应,而非视口尺寸。
核心概念
什么是 Container Query?
Container Query 允许你查询一个祖先容器元素的尺寸,并据此应用样式。这意味着组件可以自己决定:「我的父容器有多宽?我该如何渲染?」
/* 声明一个容器 */.sidebar { container-type: inline-size; container-name: sidebar;}
/* 查询容器尺寸 */@container sidebar (max-width: 300px) { .card { flex-direction: column; }}
@container sidebar (min-width: 301px) { .card { flex-direction: row; }}这段代码的含义是:当 .card 所在的 sidebar 容器宽度小于等于 300px 时,卡片纵向排列;否则横向排列。无论视口多大,组件只关心自己的容器。
container-type:容器类型
要让一个元素成为「可查询的容器」,必须显式声明 container-type。这是因为浏览器需要对容器建立尺寸封闭(size containment),以避免循环依赖。
| 值 | 含义 |
|---|---|
normal | 默认值,不是查询容器 |
size | 在行轴和块轴都建立尺寸封闭,可查询宽度和高度 |
inline-size | 仅在行轴(通常是水平方向)建立尺寸封闭,只可查询宽度 |
/* 只查询宽度(最常用) */.wrapper { container-type: inline-size;}
/* 查询宽度和高度 */.wrapper { container-type: size;}为什么 inline-size 更常用? 因为 size 要求在两个轴都建立封闭,这意味着容器的高度不能由内容撑开(intrinsic sizing 被禁用),在很多布局场景中不实用。而 inline-size 只约束宽度方向,高度仍然可以自适应内容,适用于绝大多数场景。
container-name:命名容器
当页面中有多层嵌套容器时,你需要指定查询哪个容器。container-name 就是用来给容器命名的:
.page { container-type: inline-size; container-name: page;}
.sidebar { container-type: inline-size; container-name: sidebar;}
/* 精确查询 sidebar 容器 */@container sidebar (max-width: 250px) { .nav-link { font-size: 0.875rem; }}
/* 精确查询 page 容器 */@container page (min-width: 1200px) { .hero { padding: 4rem; }}如果不指定名称,@container 会查询最近的祖先容器:
/* 查询最近的祖先容器 */@container (min-width: 400px) { .card-title { font-size: 1.5rem; }}简写属性 container
container 是 container-type 和 container-name 的简写:
/* 简写 */.sidebar { container: sidebar / inline-size;}
/* 等同于 */.sidebar { container-name: sidebar; container-type: inline-size;}@container 语法深入
基本查询语法
@container 的语法与 @media 非常相似,支持 min-width、max-width、min-height、max-height:
@container (min-width: 600px) { .card { grid-template-columns: 200px 1fr; }}
@container (max-width: 599px) { .card { grid-template-columns: 1fr; }}使用新范围语法
与现代媒体查询一样,Container Queries 也支持范围语法(range syntax),更直观:
/* 传统写法 */@container (min-width: 400px) and (max-width: 799px) { .card { /* ... */ }}
/* 范围语法 —— 更清晰 */@container (400px <= width <= 799px) { .card { /* ... */ }}
/* 单侧范围 */@container (width >= 800px) { .card { /* ... */ }}组合条件
支持 and、or、not 逻辑运算:
/* 同时满足宽度和高度条件(需要 container-type: size) */@container card-wrapper (width >= 400px) and (height >= 300px) { .card { display: grid; grid-template-rows: 200px 1fr; }}
/* 取反 */@container sidebar not (width >= 300px) { .widget { display: none; }}命名容器查询
@container sidebar (width < 300px) { .nav-item span { display: none; /* 隐藏文字,只显示图标 */ }}
@container main-content (width >= 900px) { .article { columns: 2; column-gap: 2rem; }}容器查询单位
Container Queries 引入了一套新的容器相对单位,类似于视口单位(vw、vh),但基于容器尺寸:
| 单位 | 含义 |
|---|---|
cqw | 容器宽度的 1% |
cqh | 容器高度的 1% |
cqi | 容器行轴尺寸的 1% |
cqb | 容器块轴尺寸的 1% |
cqmin | cqi 和 cqb 中较小的值 |
cqmax | cqi 和 cqb 中较大的值 |
.card-container { container-type: inline-size;}
.card-title { /* 字体大小随容器宽度缩放,但有上下限 */ font-size: clamp(1rem, 4cqi, 2rem);}
.card-image { /* 图片高度为容器宽度的 56.25%(16:9 比例) */ height: 56.25cqi; object-fit: cover;}这些单位特别适合做流式排版(fluid typography)和等比缩放,而且是基于组件容器而非视口。
实战案例
案例 1:自适应卡片组件
这是最经典的用例——一个卡片组件,根据所在容器宽度自动切换布局:
<div class="card-wrapper"> <article class="card"> <img src="cover.jpg" alt="封面" class="card__image" /> <div class="card__body"> <h3 class="card__title">文章标题</h3> <p class="card__excerpt">这是文章摘要...</p> <a href="#" class="card__link">阅读更多</a> </div> </article></div>.card-wrapper { container: card / inline-size;}
/* 默认:小容器,纵向堆叠 */.card { display: flex; flex-direction: column; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);}
.card__image { width: 100%; height: 200px; object-fit: cover;}
.card__body { padding: 1rem;}
.card__title { font-size: 1.125rem; margin-bottom: 0.5rem;}
/* 中等容器:横向排列 */@container card (width >= 400px) { .card { flex-direction: row; }
.card__image { width: 40%; height: auto; min-height: 180px; }
.card__body { flex: 1; display: flex; flex-direction: column; justify-content: center; }}
/* 大容器:增强样式 */@container card (width >= 700px) { .card__title { font-size: 1.75rem; }
.card__excerpt { font-size: 1.125rem; line-height: 1.8; }
.card__image { width: 45%; }}现在,无论这个卡片被放在全宽区域、双栏布局的一侧,还是窄窄的侧边栏里,它都能自动适应。完全不需要知道视口有多宽。
案例 2:响应式导航栏
.nav-container { container: nav / inline-size;}
.nav { display: flex; align-items: center; gap: 1rem;}
.nav-links { display: flex; gap: 0.5rem;}
.nav-label { display: inline;}
.nav-hamburger { display: none;}
/* 窄容器:隐藏文字标签,只显示图标 */@container nav (width < 600px) { .nav-label { display: none; }}
/* 更窄:切换为汉堡菜单 */@container nav (width < 400px) { .nav-links { display: none; }
.nav-hamburger { display: block; }}案例 3:仪表盘数据面板
.dashboard-panel { container: panel / inline-size;}
.stats-grid { display: grid; gap: 1rem; grid-template-columns: 1fr;}
/* 中等面板:两列 */@container panel (width >= 500px) { .stats-grid { grid-template-columns: repeat(2, 1fr); }}
/* 大面板:四列 + 图表可见 */@container panel (width >= 900px) { .stats-grid { grid-template-columns: repeat(4, 1fr); }
.stats-chart { display: block; height: 300px; }}
/* 小面板:精简模式 */@container panel (width < 300px) { .stats-item { padding: 0.5rem; }
.stats-item__label { font-size: 0.75rem; }
.stats-item__value { font-size: 1.25rem; }
.stats-item__trend { display: none; }}配合组件化开发
Container Queries 的最大价值在于它天然适配组件化架构。以 Vue 为例:
<template> <div class="product-card-container"> <article class="product-card"> <img :src="product.image" :alt="product.name" class="product-card__image" /> <div class="product-card__info"> <h3 class="product-card__name">{{ product.name }}</h3> <p class="product-card__price">¥{{ product.price }}</p> <p class="product-card__desc">{{ product.description }}</p> <button class="product-card__btn">加入购物车</button> </div> </article> </div></template>
<style scoped>.product-card-container { container: product / inline-size;}
.product-card { display: flex; flex-direction: column; border: 1px solid #e5e7eb; border-radius: 12px; overflow: hidden; transition: box-shadow 0.2s;}
.product-card:hover { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);}
.product-card__image { width: 100%; aspect-ratio: 1; object-fit: cover;}
.product-card__info { padding: 1rem;}
.product-card__name { font-size: 1rem; font-weight: 600;}
.product-card__desc { display: none;}
/* 中等容器:显示描述 */@container product (width >= 280px) { .product-card__desc { display: block; color: #6b7280; font-size: 0.875rem; margin-top: 0.5rem; }}
/* 大容器:横向布局 */@container product (width >= 500px) { .product-card { flex-direction: row; }
.product-card__image { width: 200px; aspect-ratio: auto; }
.product-card__info { flex: 1; display: flex; flex-direction: column; justify-content: center; padding: 1.5rem; }
.product-card__name { font-size: 1.25rem; }
.product-card__btn { align-self: flex-start; margin-top: 1rem; }}</style>这个组件在任何地方都能正确工作——放在三列网格里、放在侧边栏里、放在全宽 banner 里。组件自身就是响应式的。
容器查询 vs 媒体查询:何时用哪个?
| 场景 | 推荐方案 |
|---|---|
| 页面整体布局(如导航栏位置、页脚样式) | @media |
| 组件内部布局适配 | @container |
| 打印样式 | @media print |
| 暗色模式 | @media (prefers-color-scheme) |
| 嵌套在不同宽度容器中的卡片 | @container |
| 组件库 / 设计系统中的通用组件 | @container |
简单原则:页面级用 @media,组件级用 @container。
底层原理:Size Containment
Container Queries 能工作的前提是尺寸封闭(size containment)。当你设置 container-type: inline-size 时,浏览器实际上在该元素上应用了 contain: inline-size layout style。
这意味着:
- 容器的行轴尺寸不受子元素影响。 浏览器可以先确定容器宽度,再渲染子元素,避免循环依赖。
- Layout containment 确保容器内部的布局变化不会影响外部。
- Style containment 确保计数器等样式效果不会泄漏到容器外。
这也解释了为什么 container-type: size 比较少用——它在块轴也建立封闭,导致容器高度无法由内容撑开,必须显式设置高度。
/* ⚠️ 使用 size 时要注意高度 */.fixed-panel { container-type: size; height: 400px; /* 必须显式指定高度 */}
/* ✅ inline-size 更安全 */.flexible-panel { container-type: inline-size; /* 高度自动由内容决定 */}浏览器兼容性与渐进增强
截至 2026 年,所有主流浏览器都已支持 Container Queries:
- Chrome 105+
- Firefox 110+
- Safari 16+
- Edge 105+
对于需要兼容旧浏览器的项目,可以使用渐进增强策略:
/* 基础样式:所有浏览器都能工作 */.card { display: flex; flex-direction: column;}
/* 媒体查询作为 fallback */@media (min-width: 768px) { .card { flex-direction: row; }}
/* Container Query 覆盖(支持的浏览器) */@supports (container-type: inline-size) { .card-wrapper { container-type: inline-size; }
@container (width >= 400px) { .card { flex-direction: row; } }}常见陷阱与最佳实践
1. 容器不能查询自身
/* ❌ 错误:不能查询自身的尺寸来改变自身 */.box { container-type: inline-size;}@container (width >= 300px) { .box { padding: 2rem; /* 这不会应用到 .box 自身 */ }}
/* ✅ 正确:查询的是祖先容器,样式应用到后代 */.box-wrapper { container-type: inline-size;}@container (width >= 300px) { .box { padding: 2rem; }}2. 避免不必要的 size 类型
/* ❌ 除非你真的需要查询高度,否则不要用 size */.panel { container-type: size;}
/* ✅ 绝大多数场景用 inline-size */.panel { container-type: inline-size;}3. 合理命名容器
/* ❌ 不命名,依赖最近祖先,容易在嵌套时出错 */.a { container-type: inline-size; }.b { container-type: inline-size; }
@container (width >= 500px) { .target { /* 查的是哪个? */ }}
/* ✅ 明确命名 */.a { container: layout-a / inline-size; }.b { container: layout-b / inline-size; }
@container layout-a (width >= 500px) { .target { /* 明确查询 layout-a */ }}4. 一个元素可以是多个容器名
.panel { container-name: panel sidebar widget; container-type: inline-size;}
/* 以下三种查询都能匹配到同一个元素 */@container panel (width >= 300px) { /* ... */ }@container sidebar (width >= 300px) { /* ... */ }@container widget (width >= 300px) { /* ... */ }总结
CSS Container Queries 是自 Flexbox/Grid 以来最重要的 CSS 布局特性之一。它从根本上改变了我们编写响应式样式的思维方式:
- 从视口思维到容器思维:组件不再需要知道页面整体布局。
- 真正的组件封装:样式响应逻辑内聚在组件内部。
- 减少样式冗余:不再需要为不同上下文写不同的媒体查询组合。
- 更好的可维护性:组件迁移到新的布局环境时,无需修改样式。
如果你还没有在项目中使用 Container Queries,现在是时候了。从最简单的卡片组件开始,逐步迁移——你会发现代码变得更干净、更健壮、更易维护。
文章分享
如果这篇文章对你有帮助,欢迎分享给更多人!