为什么我们需要 Container Queries?
在响应式 Web 设计领域,CSS 媒体查询(Media Queries)一直是我们的核心工具。从 2010 年 Ethan Marcotte 提出响应式设计概念至今,
1 | @media |
规则几乎承担了所有自适应布局的重任。然而,随着组件化开发模式的普及,媒体查询的一个根本性缺陷越来越明显——它只能响应视口(viewport)尺寸,无法感知组件自身所在容器的尺寸。
想象这样一个场景:你编写了一个精美的卡片组件,在大屏下横向排列图片和文字,在小屏下纵向堆叠。在传统的媒体查询方案中,你需要根据视口断点来切换布局。但问题来了——当这个卡片组件被放在侧边栏中时,即使视口宽度是 1440px,侧边栏可能只有 300px 宽,卡片依然需要纵向布局。媒体查询对此无能为力。
这就是 Container Queries 诞生的核心动机:让组件能够根据自身所在容器的大小来调整样式,而不是依赖全局视口尺寸。这一特性从 2022 年开始被主流浏览器支持,标志着响应式设计从「页面级」正式迈入「组件级」时代。
Container Queries 核心语法详解
定义容器:container-type 与 container-name
Container Queries 的第一步是声明一个元素为「查询容器」。浏览器需要知道哪些元素可以被查询,以及查询的依据是什么——是容器的宽度、高度,还是两者兼备。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 /* 基础用法:声明容器类型 */
.card-wrapper {
container-type: inline-size;
/* inline-size 表示容器的行内方向尺寸(通常是宽度)可以被查询 */
}
/* 完整写法:同时指定类型和名称 */
.sidebar {
container-type: inline-size;
container-name: sidebar;
}
/* 简写属性 */
.card-wrapper {
container: card / inline-size;
/* 格式:container: [name] / [type] */
}
/* 三种容器类型 */
.width-only {
container-type: inline-size; /* 只查询行内方向尺寸 */
}
.height-only {
container-type: size; /* 可查询宽度和高度 */
}
.both-dimensions {
container-type: normal; /* 默认值,不能被查询 */
}
关于
1 | container-type |
的选择有一个重要细节:
1 | inline-size |
是最常用的类型,因为它只建立行内方向的查询约束,不会影响容器的块级布局行为。而
1 | size |
类型会让容器建立完整的格式化上下文,在某些布局中可能产生意想不到的副作用。除非你确实需要查询高度,否则请优先使用
1 | inline-size |
。
查询容器:@container 规则
定义好容器后,就可以使用
1 | @container |
规则来编写条件样式了。语法与
1 | @media |
非常相似,但查询的是容器尺寸而非视口尺寸。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38 /* 基础查询 */
@container (min-width: 400px) {
.card {
display: flex;
gap: 1rem;
}
.card-image {
width: 200px;
flex-shrink: 0;
}
}
/* 命名容器查询——当页面有多个容器时 */
@container sidebar (min-width: 300px) {
.widget {
grid-template-columns: 1fr 1fr;
}
}
/* 复杂条件组合 */
@container (min-width: 400px) and (max-width: 700px) {
.card {
display: grid;
grid-template-columns: 120px 1fr;
}
}
/* 查询容器高度(需要 container-type: size) */
.hero-section {
container-type: size;
container-name: hero;
}
@container hero (min-height: 500px) {
.hero-content {
font-size: 2.5rem;
}
}
一个关键的区别:
1 | @media |
查询的是视口的
1 | width |
/
1 | height |
,而
1 | @container |
查询的是容器的
1 | inline-size |
/
1 | block-size |
。使用逻辑属性而非物理方向,是为了适配垂直书写模式(如中文竖排、日文纵书)等场景。
实战案例:构建真正自适应的卡片组件
理论说了这么多,让我们通过一个完整的实战案例来体会 Container Queries 的威力。我们将构建一个新闻卡片组件,它能在三种布局之间平滑切换:
- 窄容器(< 300px):紧凑纵向布局,小图在上、标题在下
- 中等容器(300px – 600px):横向布局,左侧图片、右侧内容
- 宽容器(> 600px):大图横向布局,增加摘要和元信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 <!-- HTML 结构 -->
<div class="card-container">
<article class="news-card">
<div class="card-media">
<img src="news.jpg" alt="新闻图片" />
</div>
<div class="card-body">
<span class="card-tag">技术前沿</span>
<h3 class="card-title">CSS Container Queries 改变了组件设计范式</h3>
<p class="card-summary">从视口级响应式到组件级响应式,这是一次根本性的范式转变...</p>
<div class="card-meta">
<time>2026-08-09</time>
<span>阅读 5 分钟</span>
</div>
</div>
</article>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79 /* CSS:容器声明与基础样式 */
.card-container {
container: card / inline-size;
}
.news-card {
display: flex;
flex-direction: column;
border-radius: 12px;
overflow: hidden;
background: #fff;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.card-media img {
width: 100%;
height: 180px;
object-fit: cover;
}
.card-body {
padding: 1rem;
}
.card-title {
font-size: 1rem;
line-height: 1.4;
margin: 0.5rem 0;
}
/* 窄容器下隐藏摘要和元信息 */
.card-summary,
.card-meta {
display: none;
}
/* 中等容器:横向布局 */
@container (min-width: 300px) {
.news-card {
flex-direction: row;
}
.card-media {
width: 140px;
flex-shrink: 0;
}
.card-media img {
height: 100%;
}
.card-title {
font-size: 1.1rem;
}
.card-meta {
display: flex;
gap: 0.75rem;
margin-top: 0.5rem;
font-size: 0.85rem;
color: #666;
}
}
/* 宽容器:完整布局 */
@container (min-width: 600px) {
.card-media {
width: 240px;
}
.card-title {
font-size: 1.3rem;
}
.card-summary {
display: block;
font-size: 0.9rem;
color: #555;
line-height: 1.6;
margin: 0.5rem 0;
}
.card-meta {
font-size: 0.9rem;
}
}
这段代码的精髓在于:同一个
1 | .news-card |
组件,被放在页面主内容区、侧边栏、弹窗中时,无需任何 CSS 修改或 JavaScript 判断,就能自动适配不同的容器宽度。这是传统媒体查询完全做不到的。
Container Query Units:新的 CSS 单位
与 Container Queries 一起推出的还有一组新的 CSS 长度单位,它们参照容器尺寸而非视口尺寸进行计算:
| 单位 | 含义 | 参照对象 | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
容器宽度的 1% | 最近查询容器的宽度 | ||||||||
|
容器高度的 1% | 最近查询容器的高度 | ||||||||
|
容器行内尺寸的 1% | 最近查询容器的行内方向 | ||||||||
|
容器块级尺寸的 1% | 最近查询容器的块级方向 | ||||||||
|
和
中较小的值 |
类似
,但基于容器 |
||||||||
|
和
中较大的值 |
类似
,但基于容器 |
这些单位的实用价值极高。比如,你想让组件内的字体大小随容器宽度平滑缩放:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 .card-container {
container: card / inline-size;
}
.card-title {
/* 在 400px 容器中约 24px,在 800px 容器中约 36px */
font-size: clamp(1rem, 4.5cqi, 2.25rem);
}
.card-padding {
/* 间距也随容器自适应 */
padding: 2cqi;
}
.card-gap {
gap: 1.5cqi;
}
结合
1 | clamp() |
函数使用 Container Query 单位,可以创建真正流畅的自适应排版效果——既不会在小容器中过小,也不会在大容器中过大,而是在合理的范围内平滑过渡。这种方案比传统的断点式跳变更接近设计师心中的「完美响应式」。
Container Queries 与设计系统的深度集成
Container Queries 最大的价值体现在设计系统(Design System)中。在一个成熟的设计系统里,组件需要被复用到各种上下文中——主内容区、侧边栏、对话框、抽屉面板——而开发者不应该每次都写一套新的媒体查询。
组件 Token 与容器查询结合
现代设计系统通常使用 CSS 自定义属性(Design Tokens)来管理设计变量。Container Queries 可以与 Design Tokens 深度结合:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36 /* 设计系统 Token 定义 */
:root {
--space-xs: 0.25rem;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 1.5rem;
--space-xl: 2rem;
--font-sm: 0.875rem;
--font-md: 1rem;
--font-lg: 1.25rem;
--font-xl: 1.5rem;
}
/* 通用卡片容器 */
.ds-card-container {
container: ds-card / inline-size;
}
.ds-card {
padding: var(--space-md);
font-size: var(--font-md);
}
@container ds-card (min-width: 400px) {
.ds-card {
padding: var(--space-lg);
font-size: var(--font-lg);
}
}
@container ds-card (min-width: 700px) {
.ds-card {
padding: var(--space-xl);
font-size: var(--font-xl);
}
}
Shadow DOM 中的 Container Queries
Web Components 使用 Shadow DOM 进行样式隔离,Container Queries 在 Shadow DOM 中同样有效——这是它相比 JavaScript 方案的一大优势:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 class ResponsiveCard extends HTMLElement {
constructor() {
super();
const shadow = this.attachShadow({ mode: 'open' });
shadow.innerHTML = `
<style>
:host {
display: block;
container: card-host / inline-size;
}
.inner {
padding: 1rem;
background: white;
border-radius: 8px;
}
@container (min-width: 400px) {
.inner {
display: grid;
grid-template-columns: 1fr 2fr;
gap: 1rem;
}
}
</style>
<div class="inner">
<slot name="media"></slot>
<slot name="content"></slot>
</div>
`;
}
}
customElements.define('responsive-card', ResponsiveCard);
实战进阶:Container Queries 的常见陷阱与解决方案
陷阱一:容器选择错误
1 | @container |
查询会沿着 DOM 树向上查找最近的具有
1 | container-type |
的祖先元素。如果你在错误的位置声明了容器,查询结果会出乎意料:
1
2
3
4
5
6
7
8
9
10
11
12 /* 错误示范:容器声明在了组件内部 */
.card {
container-type: inline-size; /* ❌ 错误!*/
}
/* 正确做法:容器声明在组件的包裹元素上 */
.card-wrapper {
container-type: inline-size; /* ✅ 正确 */
}
.card {
/* 这里的样式会根据 .card-wrapper 的宽度来查询 */
}
原则很简单:容器是组件的「外部环境」,不是组件本身。你需要查询的是组件所在的可用空间,而不是组件自身渲染后的尺寸。
陷阱二:container-type 对布局的影响
1 | container-type: size |
会对容器建立完整的尺寸约束,这意味着容器的高度不再由内容撑开——这会破坏许多常见的布局模式:
1
2
3
4
5
6
7
8
9 /* 如果你只需要查询宽度,永远用 inline-size */
.sidebar {
container-type: inline-size; /* ✅ 安全 */
}
/* size 类型会导致高度塌陷问题 */
.content {
container-type: size; /* ⚠️ 需要显式设置高度 */
}
陷阱三:循环依赖
容器查询的条件和结果不能形成循环。例如,如果容器的宽度取决于子元素的内容宽度,而子元素的样式又取决于容器宽度,浏览器会中断这个循环:
1
2
3
4
5
6
7
8
9
10
11 /* 避免这样的循环依赖 */
.container {
container-type: inline-size;
width: fit-content; /* ⚠️ 宽度取决于子元素 */
}
@container (min-width: 300px) {
.child {
min-width: 400px; /* ⚠️ 这会撑大容器,形成循环 */
}
}
浏览器处理循环依赖的方式是:容器的查询尺寸基于「应用容器样式之前」的布局状态。这意味着容器查询的断点触发是稳定的,不会来回抖动。但这也意味着你需要确保容器尺寸不会因为查询结果而产生级联变化。
浏览器兼容性与渐进增强策略
截至 2026 年,Container Queries 已获得所有主流浏览器的支持(Chrome 105+、Firefox 110+、Safari 16+、Edge 105+)。全球浏览器覆盖率已超过 92%。但如果你仍需兼容旧版浏览器,以下是推荐的渐进增强策略:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 /* 基础样式:所有浏览器都能渲染 */
.news-card {
display: flex;
flex-direction: column;
padding: 1rem;
}
/* 媒体查询兜底:旧浏览器的响应式方案 */
@media (min-width: 768px) {
.news-card {
flex-direction: row;
}
}
/* 容器查询:现代浏览器优先使用 */
@supports (container-type: inline-size) {
.card-container {
container-type: inline-size;
}
/* 重置媒体查询的修改 */
@media (min-width: 768px) {
.news-card {
flex-direction: column; /* 恢复默认,由容器查询控制 */
}
}
@container (min-width: 300px) {
.news-card {
flex-direction: row;
}
}
}
使用
1 | @supports (container-type: inline-size) |
可以精确检测浏览器是否支持 Container Queries,并在此基础上提供增强体验,同时保证旧浏览器通过媒体查询获得可接受的降级方案。
Container Queries 与 CSS :has() 选择器的协同
Container Queries 和
1 | :has() |
选择器是近年 CSS 最重要的两个新特性。它们可以协同工作,实现更强大的组件自适应能力:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 /* 根据子元素数量动态调整网格布局 */
.grid-container {
container: grid / inline-size;
display: grid;
gap: 1rem;
}
/* 有 2-3 个子项时使用双列 */
.grid-container:has(.grid-item:nth-child(2):last-child),
.grid-container:has(.grid-item:nth-child(3):last-child) {
grid-template-columns: repeat(2, 1fr);
}
/* 有 4 个以上子项时使用三列(且容器足够宽) */
@container grid (min-width: 600px) {
.grid-container:has(.grid-item:nth-child(n+4)) {
grid-template-columns: repeat(3, 1fr);
}
}
这种组合让 CSS 首次具备了「根据内容和环境双重条件」来调整布局的能力。以前这类需求只能通过 JavaScript 实现,现在纯 CSS 就能完成,且性能更优、更可预测。
性能优化与最佳实践
Container Queries 在性能方面有几个值得注意的要点:
- 避免过度嵌套容器:每个
1container-type
声明都会创建一个查询容器,浏览器需要为每个容器计算尺寸。过深的嵌套会增加布局计算的开销。通常一层容器就够了。
- 使用命名容器减少歧义:当页面中有多个容器时,使用
1container-name
可以让浏览器直接定位目标容器,避免逐层向上搜索。
- 合理选择断点:容器查询的断点应该基于组件的设计需求,而不是沿用媒体查询的固定断点(768px、1024px 等)。每个组件有自己的「布局临界点」。
- 优先使用 inline-size:如前所述,
1inline-size
对布局的影响最小,性能开销也最低。只在真正需要查询高度时才使用
1size。
- 与 CSS containment 结合:容器声明隐式启用了 CSS containment。这意味着容器内的布局变化不会影响容器外的元素。这是性能优化的关键——浏览器可以独立地重新计算容器内的布局。
1
2
3
4
5
6
7
8
9
10
11
12
13 /* 高性能容器声明示例 */
.card-container {
container: card / inline-size;
/* 隐式包含:layout inline-size style */
/* 布局变化被限制在容器内,不会引起全局重排 */
}
/* 如果容器有固定的最大宽度,可以进一步优化 */
.sidebar-card-container {
container: sidebar-card / inline-size;
contain: inline-size layout style paint;
/* 显式添加 paint containment,减少渲染层 */
}
总结:响应式设计的新范式
CSS Container Queries 不是媒体查询的替代品,而是其自然的进化与补充。媒体查询处理页面级布局(如导航栏的展开/收起、全宽/侧边栏布局切换),容器查询处理组件级布局(如卡片在窄/宽容器中的排列方式)。两者结合使用,才是完整的现代响应式方案。
从工程角度看,Container Queries 最大的意义在于让组件真正成为可复用的独立单元。一个设计良好的组件不再需要知道它将被放在页面的哪个位置——它只需要关注自己能获得多少空间,并据此做出最优的布局决策。这正是组件化开发一直想要、却直到现在才真正拥有的能力。
对于正在维护设计系统的团队来说,现在是认真考虑引入 Container Queries 的最佳时机。浏览器支持已经足够成熟,API 稳定且不会有大变动,渐进增强策略也很清晰。从最常用的卡片、列表项、导航模块开始,逐步将响应式逻辑从媒体查询迁移到容器查询,你会发现组件的可复用性和可维护性都有显著提升。
汤不热吧