响应式设计十年来一直依赖媒体查询(Media Queries),它根据视口尺寸调整布局。但当我们将一个组件放在不同宽度的容器中时——比如一个卡片在狭窄的侧边栏和宽阔的主内容区——媒体查询无能为力,因为它只关心视口,不关心组件自身的上下文。而CSS 容器查询(Container Queries)彻底改变了这一局面,允许我们基于父容器的尺寸来定义组件的样式,实现真正意义上的组件级响应式。本文将通过三个实战案例,带你从零掌握这项革命性技术。
容器查询解决了什么痛点?
假设我们开发了一个“推荐卡片”组件,它可能出现在产品列表页(宽1200px)、博客侧边栏(宽300px)或弹窗内(宽500px)。传统的媒体查询只能根据视口宽度改变样式,导致同一组件在不同上下文中表现僵化。我们不得不为每种场景编写额外的 BEM 修饰类,或者依靠 JavaScript 动态检测容器宽度。这些方案增加了维护成本,且破坏了组件的封装性。
容器查询允许我们在组件内部直接定义:“当我的父容器宽度大于600px时,显示为水平布局;否则显示为垂直布局”。样式逻辑和组件自身绑定,不依赖于全局视口,从而实现了真正的可复用组件。所有现代浏览器(Chrome 105+、Safari 16+、Firefox 103+)均已支持该特性,可以放心地在生产环境使用。
核心概念:定义容器与@container规则
使用容器查询需要两步:首先,在父元素上声明 container-type,将其定义为查询容器;然后,使用 @container 规则编写针对该容器的样式。
最简单的定义方式:
.parent {
container-type: inline-size;
}
inline-size 表示根据容器的内联尺寸(通常是水平书写模式下的宽度)进行查询。你还可以使用 size 同时查询宽度和高度,但使用较少。另外可以通过 container-name 为容器命名,以便嵌套时精确指定。
然后在子组件的样式中使用 @container:
@container (min-width: 500px) {
.child {
display: flex;
}
}
此处 min-width: 500px 参考的是最近祖先容器的宽度,而非视口。如果需要指定某个命名容器,可以写为 @container my-container (min-width: 500px)。
另外,CSS 还引入了容器查询单位:cqw(容器宽度的1%)、cqh(容器高度的1%)、cqmin、cqmax 等。它们类似于视口单位 vw、vh,但是相对于查询容器,在组件内部使用这些单位可以实现更动态的尺寸计算。
案例一:自适应卡片组件——从单列到多列
这是容器查询最经典的用法。一个“产品卡片”组件,在窄容器中图片和文字上下排列,在宽容器中左右排列,并能自动调整内部元素的尺寸。
首先,HTML 结构:
<div class="card-wrapper">
<div class="card">
<img src="product.jpg" alt="商品" class="card-img">
<div class="card-body">
<h3>商品名称</h3>
<p>描述文本</p>
<button>立即购买</button>
</div>
</div>
</div>
关键的 CSS:
/* 父元素声明为容器 */
.card-wrapper {
container-type: inline-size;
container-name: card-container;
}
/* 默认样式(窄容器) */
.card {
display: flex;
flex-direction: column;
border: 1px solid #eee;
border-radius: 8px;
overflow: hidden;
}
.card-img {
width: 100%;
height: auto;
}
.card-body {
padding: 1rem;
}
/* 当容器宽度大于 500px 时切换为水平布局 */
@container card-container (min-width: 500px) {
.card {
flex-direction: row;
align-items: center;
}
.card-img {
width: 40%;
max-width: 200px;
}
.card-body {
flex: 1;
}
}
/* 当容器宽度大于 700px 时进一步增大字体和间距 */
@container card-container (min-width: 700px) {
.card-body h3 {
font-size: 1.5rem;
}
.card-body p {
font-size: 1rem;
line-height: 1.6;
}
}
现在,无论 .card-wrapper 被放在主内容区域(例如 800px 宽)还是侧边栏(例如 300px 宽),卡片都会自动选择合适的布局。你可以将同样的组件放到不同页面,不需要添加任何修饰类或额外媒体查询,组件自身决定了如何展示,封装性达到极致。
这个案例还展示了 容器查询级联——多个断点针对同一容器,逐步优化组件样式,与传统的媒体查询思维相似,但作用域限定在容器内。
案例二:动态排版与间距——容器查询单位
容器查询单位 cqw 和 cqh 使得组件内部的尺寸可以随容器按比例缩放,非常适合动态调整字体大小、内边距和圆角。例如,制作一个信息卡片,其标题大小和间距与容器宽度成比例,而无需编写多个断点。
我们继续使用上面的卡片结构,简化样式:
.card {
container-type: inline-size;
padding: 2cqw; /* 内边距为容器宽度的2% */
font-size: clamp(0.9rem, 3cqw, 1.3rem); /* 限制范围 */
border-radius: 2cqw;
}
.card h3 {
font-size: clamp(1.2rem, 5cqw, 2rem);
margin-bottom: 1cqw;
}
.card p {
margin-bottom: 2cqw;
line-height: 1.5;
}
.card button {
padding: 1cqw 3cqw;
font-size: inherit;
border-radius: 1cqw;
}
这里使用了 clamp() 函数结合 cqw,确保字体在任何容器宽度下都能保持可读性,同时拥有流体缩放效果。当容器宽度为 300px 时,3cqw 等于 9px,因此 clamp(0.9rem, 3cqw, 1.3rem) 会取 0.9rem(约14px),保证最小可读性;当容器为 800px 时,3cqw 为 24px,但被限制在 1.3rem(约20px),防止过大。这种技术被称为组件级流体排版,在此之前只能通过视口单位实现,但现在可以基于父容器动态调整,灵活性大大增加。
你也可以单独使用 cqw 来设置 width、height、margin 等,让组件内部元素完全根据父容器进行等比缩放,这在仪表盘、图表组件等场景中尤为有用。
浏览器兼容性与渐进增强
截至2024年底,容器查询已在所有主流浏览器中得到原生支持,覆盖率超过90%。对于仍在使用旧版本浏览器的用户,我们可以提供渐进增强策略:先编写一套不依赖容器查询的基础样式,然后使用 @supports 包裹容器查询样式。
/* 基础样式:对所有浏览器有效 */
.card {
display: flex;
flex-direction: column;
}
/* 容器查询增强:仅对支持的浏览器生效 */
@supports (container-type: inline-size) {
.card-wrapper {
container-type: inline-size;
}
@container (min-width: 500px) {
.card {
flex-direction: row;
}
}
}
这样,在不支持的浏览器中,卡片始终以垂直布局显示,仍可正常使用;在支持的浏览器中获得优化体验。由于容器查询不像 Flexbox 或 Grid 那样影响基本布局结构,大多数情况下缺少它并不会破坏页面,因此天然适合渐进增强。
总结
CSS 容器查询将响应式设计的控制权从视口交还给组件本身,是设计系统迈向真正模块化的重要一步。通过本文的三个案例,我们学习了如何定义查询容器、使用 @container 规则编写组件级样式、利用容器查询单位实现流体缩放,以及在嵌套容器中精确指定查询目标。现在,你可以将容器查询应用到自己的项目中,构建出更独立、更灵活、更易维护的组件。拥抱容器查询,让响应式设计从页面级别下沉到每一个细微的组件中去。

