做组件库的同学一定遇到过这个场景:同一张卡片,放在主内容区里舒展大气,一丢进侧边栏就挤成一团。你用媒体查询写了三档响应式,结果组件换个地方放就全乱套——因为媒体查询只能看见视口,看不见组件所处的容器。
组件的响应式理应由容器决定,而不是由屏幕决定。这就是容器查询(Container Queries)要解决的问题。2023 年起它已在所有主流浏览器落地,如今是我在组件库里默认使用的方案。
20 秒上手
总共就两步:先在父元素上声明容器,再按容器尺寸写样式。
/* 第一步:声明容器 */
.card-wrap {
container-type: inline-size;
container-name: card;
}
/* 第二步:按容器宽度写样式 */
@container card (min-width: 480px) {
.card { display: flex; gap: 16px; }
.card img { width: 160px; }
}
container-name 可以省略,省略后 @container (min-width: 480px) 会匹配最近的容器祖先。
实战一:文章卡片的三段式布局
本站的首页文章卡片就是典型例子:窄容器纵向排列,宽容器图文横排,再宽一点摘要显示两行。三个断点全部基于卡片自身宽度:
.post-wrap { container-type: inline-size; }
@container (max-width: 419px) {
.post-card img { width: 100%; aspect-ratio: 16/9; }
}
@container (min-width: 420px) {
.post-card { display: grid; grid-template-columns: 120px 1fr; }
}
@container (min-width: 640px) {
.post-card { grid-template-columns: 200px 1fr; }
.post-card .excerpt {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
}
把这张卡片放进主栏、侧栏还是弹窗,布局都由容器说了算,组件从此可以放心到处搬。
实战二:表单标签自适应宽度
后台系统里表单经常被拖来拖去。用容器查询让 label 在窄容器里换到输入框上方:
.field-wrap { container-type: inline-size; }
.field { display: flex; align-items: center; gap: 12px; }
.field label { flex: 0 0 96px; text-align: right; }
@container (max-width: 480px) {
.field { flex-direction: column; align-items: stretch; }
.field label { text-align: left; }
}
实战三:cqw,容器里的「rem」
容器查询单位常被忽略:cqw / cqi 是容器宽度的 1%,cqh / cqb 对应高度。组件标题想随容器等比缩放,再也不用 JS:
.widget h3 {
/* 标题始终约为容器宽度的 6%,且被夹在 16px ~ 28px 之间 */
font-size: clamp(16px, 6cqi, 28px);
}
老浏览器的降级方案
.card { display: block; }
@supports (container-type: inline-size) {
.card-wrap { container-type: inline-size; }
@container (min-width: 480px) {
.card { display: flex; }
}
}
思路是「先写一个能用的默认布局,再用 @supports 渐进增强」。Safari 16 之前的份额如今已经很少,多数项目甚至可以不做降级。
三个容易踩的坑
- 容器不能查询自己。
@container里的样式要写在容器的子元素上,写在声明容器的那一层是无效的; container-type: inline-size会应用布局、样式和行内尺寸的包含(containment),同时建立新的包含块——子元素的position: fixed会改为相对容器定位,弹层组件要留意;- 容器查询查的是尺寸,目前也支持
style()查询(如根据容器颜色变量切换样式),但浏览器支持仍在推进中,生产环境慎用。
写在最后
媒体查询负责「页面级」布局,容器查询负责「组件级」布局,两者配合才是完整的响应式体系。把组件的断点还给组件本身,你会发现 UI 代码突然清爽了很多。