过去十多年里,我们习惯把响应式设计理解为“屏幕宽度适配”:手机一栏,平板两栏,桌面三栏。但当一个卡片组件同时出现在商品列表、侧边栏、弹窗和全宽详情页里时,屏幕宽度已经无法回答最核心的问题——这个组件自己有多少空间?
容器查询改变的并不只是 CSS 语法,而是一个设计模型:让组件根据所处容器的大小,而不是整个视口的大小来调整自己。
为什么媒体查询不够用
媒体查询描述的是浏览器窗口。它知道屏幕有多宽,却不知道某个卡片此刻被塞进了 320px 的侧栏,还是铺在 960px 的主内容区。于是我们经常做两件麻烦事:
- 为同一个组件编写多套修饰类,例如
.card--compact、.card--wide,再在页面布局层手动切换; - 把原本独立的组件拆成不同版本,最后发现维护的是四份几乎相同的模板。
这些方案不是不能用,只是它们把“组件怎么响应”的责任交给了使用组件的页面。页面越多,组合越复杂,类名和样式就越容易失控。
媒体查询回答“屏幕有多大”,容器查询回答“我现在有多少空间”。这是两种完全不同的问题。
— 组件级响应式的核心转变
从一个卡片开始
先给目标容器建立查询上下文。我们不需要改变组件的 DOM,只需要在它外层的父元素上声明:
/* 父级成为具名的 intrinsic size 容器 */
.card-slot {
container: card / inline-size;
}
/* 默认:纵向信息流 */
.card {
display: flex;
flex-direction: column;
}
/* 容器足够宽时:切换成横向布局 */
@container card (min-width: 38rem) {
.card {
flex-direction: row;
}
.card__media {
inline-size: 40%;
flex: none;
}
}
container: card / inline-size 是简写,等号左边是容器名,右边是容器类型。inline-size 表示只沿着文字流向的轴建立查询尺寸,通常就是我们最关心的宽度;它不会强迫父级拥有固定高度。
这里有一个容易被忽略的细节:建立查询上下文的是父元素,查询的是父元素,而样式作用于父元素内部的后代。组件不能查询自己。这样做看似绕,其实非常合理——组件的可用空间由布局决定,而布局正是由容器提供的。
容器单位:让尺寸真正有上下文
容器查询之后,百分比已经不够用了。如果卡片在 300px 的侧栏里,图片却按视口宽度计算,它仍然会溢出。容器单位解决的正是这个问题:cqw、cqh、cqi、cqb、cqmin、cqmax 分别对应最近查询容器的宽、高、行内轴、块轴以及较小的边界。
.card__title {
/* 跟随容器宽度变化,但保留可读性边界 */
font-size: clamp(1.125rem, 4cqi, 2rem);
}
.card__cover {
aspect-ratio: 4 / 3;
border-radius: clamp(4px, 2cqi, 18px);
}
容器单位很容易让人兴奋,但我会刻意克制。字号、圆角、间距仍然需要明确的设计边界,clamp() 是必要的护栏。容器单位不是为了让每个值都“随宽度缩放”,而是为了让那些确实需要跟随组件尺寸变化的值拥有正确参照。
三个常见误区
1. 忘了给容器命名
一个组件里往往有多个查询容器。商品卡片里可能有图片容器、信息容器,再嵌套一个标签容器。如果不命名,最近容器生效,查询可能命中了错误层级。名字不必多,但关键层级应该具名,例如 container: media / inline-size、container: card / inline-size。
2. 使用 container-type: size 却不给高度
inline-size 只在行内轴施加尺寸约束,容器高度仍由内容决定;size 会同时约束两个轴,如果父级没有明确高度,容器可能失去正常的内容撑开行为。除非你确实需要按高度查询,否则优先使用 inline-size。
3. 把它当成媒体查询的替代品
页面级布局——例如全站导航、主内容与侧栏的切换——仍然适合媒体查询或现代 Grid 的自适应能力。容器查询负责组件的局部决策,媒体查询负责页面的整体编排。两者关心不同层次,混用不是问题,职责不清才是问题。
如果一个样式变化只取决于组件被放进了多大的空间,用容器查询;如果它取决于整页的布局模式、设备输入方式或用户偏好,用媒体查询。不要为了统一技术栈,强迫所有响应式逻辑住在同一层。
从“断点驱动”转向“约束驱动”
容器查询最成熟的用法,不是把旧的媒体查询断点原封不动搬进组件,而是重新定义组件的内在约束:标题需要多宽才不至于断成三行?图片在什么尺寸下会失去意义?按钮在窄容器里应该变成图标,还是继续保留文字?
这让设计系统里的组件更像一个自包含的能力单元。它知道自己从紧凑到宽裕的几种形态,却不假设自己总会出现在某个页面位置。页面负责分配空间,组件负责在给定空间里做最好的表达。
我的实践原则是:外层布局优先使用流式 Grid 与 Flex,组件内部再用容器查询处理关键形态切换。这样大多数尺寸变化根本不需要断点,只有真正发生布局语义变化时,才引入显式规则。
写在最后
容器查询真正有价值的地方,不是少写几行 CSS,而是把“适配压力”放回到了正确的责任边界。组件不再需要猜测自己会在哪个页面、哪个屏幕尺寸出现,它只需要回答一个更稳定、更可测试的问题:在当前容器里,我该如何表现?
当这种思路成为默认,响应式设计会从“屏幕尺寸清单”变成“组件形态系统”。这也许才是它带来的最大变化。