CSS 容器查询实战:让组件真正自适应
同一个卡片组件,放在宽栏里横排很好看,塞进侧栏就挤成一团。问题的根源不是屏幕太窄,而是组件不知道自己有多宽。
一、媒体查询解决不了组件级适配
@media 查询的永远是视口宽度。桌面端视口 1440px,主内容区和侧边栏都处在同一个媒体查询断点下,
但它们的可用宽度可能相差三倍。为了让卡片在侧栏里也能正常显示,过去只能给组件加一堆
.is-narrow 之类的修饰类,由外部容器决定组件长什么样。
这违背了组件的封装原则:组件应该根据自己被放置的空间自动调整,而不是要求使用方事先知道该传什么类名。 容器查询(Container Queries)解决的正是这件事。
二、最小可用示例
使用容器查询分两步:先声明「谁是容器」,再在容器内部写 @container 规则。
/* 1. 声明容器,并告诉浏览器要查询哪个维度 */
.card-list {
container-type: inline-size;
}
/* 2. 当最近的容器宽度 ≥ 480px 时生效 */
@container (min-width: 480px) {
.card {
display: grid;
grid-template-columns: 148px 1fr;
align-items: center;
}
}
与媒体查询不同,@container 的条件针对的是最近的祖先容器,
而不是视口。所以只要把同一张卡片放进侧栏,它自然就会退回窄布局,完全不需要额外的类名。
三、container-type 到底在声明什么
| 取值 | 含义 | 适用场景 |
|---|---|---|
normal |
不是查询容器(但仍可作为样式容器) | 只想用容器单位 |
inline-size |
按行内方向尺寸查询,通常就是宽度 | 绝大多数卡片、列表布局 |
size |
同时按宽高查询 | 需要高度自适应的独立区域 |
日常开发中 inline-size 已经够用,而且它不会让容器在块方向上失去内容撑开的能力,
相比 size 更安全——后者会让容器在两个方向上都需要显式尺寸,容易出现高度塌陷。
四、给容器起名字
嵌套容器很常见:页面有布局容器,卡片列表自己也是容器。当 @container 未指定名称时,
它匹配的是最近的容器,这未必是你想要的那一个。此时可以为容器命名:
.layout {
container: page / inline-size;
}
.card-list {
container: cards / inline-size;
}
@container cards (min-width: 640px) {
.card {
grid-template-columns: 180px 1fr;
}
}
container 是 container-name 与 container-type 的简写,
顺序是「名称 / 类型」。命名之后,规则就只会匹配对应名称的祖先容器,嵌套层级再深也不会误判。
五、完整实战:自适应卡片
下面是一份可以直接使用的实现,包含三种形态:窄屏纵向、中等横排、宽屏双列信息。
<ul class="card-list">
<li class="card">
<img class="card__cover" src="/cover.webp" alt="" />
<div class="card__body">
<h3 class="card__title">组件级响应式布局</h3>
<p class="card__desc">根据容器宽度自动切换排布方式。</p>
</div>
</li>
</ul>
.card-list {
container: cards / inline-size;
display: grid;
gap: 16px;
}
.card {
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 14px;
background: #fff;
}
/* 容器达到 420px:图片与文字横排 */
@container cards (min-width: 420px) {
.card {
flex-direction: row;
}
.card__cover {
width: 160px;
flex-shrink: 0;
}
}
/* 容器达到 720px:文字区域内部再分两列 */
@container cards (min-width: 720px) {
.card__body {
display: grid;
grid-template-columns: 1fr auto;
align-items: center;
gap: 24px;
padding: 20px 24px;
}
.card__desc {
margin: 0;
}
}
整个组件没有任何 @media,也不依赖外部传入的修饰类。
它被放进 300px 宽的侧栏就纵向排列,被放进 900px 宽的主区域就自动展开成横排。
六、容器单位:让字号跟着容器走
容器查询还带来了新的长度单位,最常用的是 cqw(容器宽度的 1%)与 cqi
(行内方向的 1%)。它们让字号、间距可以相对于容器缩放:
.hero-title {
font-size: clamp(20px, 4cqi, 40px);
}
.card__body {
padding: 4cqi 5cqi;
}
需要注意,cqw 依赖于最近的查询容器。如果找不到任何容器,它会回退到视口单位,
行为上等同 vw,调试时容易误以为设置没生效。
七、与媒体查询协作,而不是取代它
两者职责不同,最佳实践是分层使用:
- 页面级布局(导航折叠、栅格列数、侧栏是否显示)继续用
@media。 - 组件内部排布(图片位置、文字列数、按钮大小)交给
@container。
这样组件在任意上下文中都能自洽,页面只需要关心整体结构,维护成本会明显下降。
八、兼容性与降级
容器查询在现代浏览器中已得到良好支持。若仍需照顾较旧的内核,可以用 @supports
做渐进增强,保证基础布局始终可用:
.card {
display: flex;
flex-direction: column;
}
@supports (container-type: inline-size) {
.card-list {
container: cards / inline-size;
}
@container cards (min-width: 420px) {
.card {
flex-direction: row;
}
}
}
没有容器查询支持的浏览器会停留在纵向布局,功能不受影响,只是视觉上少了一点自适应能力。
九、几个容易踩的坑
- 容器不能查询自身。
@container只匹配祖先容器,给元素自己加container-type再查询自己是不生效的。 - container-type 会创建包含块。它会让内部的绝对定位元素以容器为参照,同时影响尺寸计算,改造老项目时要留意。
- 不要滥用 size。除非确实需要按高度适配,否则优先
inline-size。 - 命名容器有助于可维护性。组件库尤其如此,避免被外层布局容器意外匹配。
十、小结
容器查询把「响应式」从页面级下沉到了组件级。它不会让媒体查询消失,但会让组件的边界更清晰: 组件只关心自己能拿到多少空间,页面只关心如何分配空间。这种职责划分,正是设计系统能够规模化的前提。