CSS 容器查询实战 组件级响应式的正确粒度
媒体查询按视口尺寸断,但组件关心的其实是"我有多少空间"。同一个卡片放进侧栏、放进列表、放进首页大图区,需要的是不同布局,而它们的视口宽度可能完全一样。容器查询把判断依据从视口换成容器,让组件真正自治。
一、最小可用语法
.card-wrap {
container-type: inline-size; /* 声明为容器:只按行内尺寸查询 */
container-name: card;
}
@container card (min-width: 420px) {
.card { grid-template-columns: 96px 1fr; }
.card__desc { display: -webkit-box; }
}
要点:
container-type: inline-size是主流选择,它不影响块向尺寸,副作用最小;size会同时约束两个轴向,容易引发尺寸循环;- 查询打在容器的子元素上,容器自身不能被自己查询命中(这是设计使然,避免循环);
container-name在多容器嵌套时必须显式命名,否则最近的匿名容器会被匹配。
二、容器查询的副作用清单
container-type: inline-size 会创建 inline formatting context,并且是一个 size containment。三件常被绊到的事:
- 绝对定位祖先变化:containment 会让该元素成为 containing block,内部
position: absolute相对它而不是页面定位; - 内部百分比宽度依旧相对容器,但容器自身高度不参与查询,靠
cqh/cqw单位才有块向; - z-index/层叠上下文:容器查询本身不创建层叠上下文,但若同时用了
contain: paint就会创建,弹层渲染顺序可能变。
.card-wrap { container-type: inline-size; }
.card__badge { position: absolute; } /* 现在以 .card-wrap 为定位基准 */
需要脱离的绝对定位元素要挂到容器外,或改用 position: fixed + 显式坐标。
三、容器单位与流式排版
@container (min-width: 300px) {
.card__title { font-size: clamp(1rem, 1rem + 0.4cqw, 1.375rem); }
.card__meta { gap: 2cqi; }
}
cqw/cqi/cqb/cqh 相对容器尺寸,cqi 是行内轴向(多数情况下的正确选择)。和 clamp() 配合能得到纯流式的字号与间距,减少断点数量。
注意:容器单位在字体上易过冲——容器很大时字号失控。clamp() 的上限一定要写死。
四、组件自治:真正的设计价值
传统做法要把布局变体做成 prop:
<Card variant="sidebar" /> // 组件被使用位置污染
容器查询后组件自己决定:
.card {
container-type: inline-size;
display: grid;
grid-template-columns: 1fr;
gap: .5rem;
}
@container (min-width: 280px) {
.card { grid-template-columns: 64px 1fr; align-items: center; }
}
@container (min-width: 520px) {
.card { grid-template-columns: 140px 1fr; gap: 1rem; }
.card__excerpt { display: block; }
}
同一个 <Card> 塞进任何栅格都自适应,variant 只保留真正语义差异(如"精选")。这让设计系统与布局彻底解耦——设计系统负责组件在小/中/大空间下的表现,页面负责栅格。
五、与栅格、媒体查询的分工
三者不是替代关系,是不同抽象层级:
媒体查询 → 设备/视口级别:导航折叠、页面外边距、栅格列数
容器查询 → 组件级别:卡片、表格、表单在可用空间下的形态
栅格/子网格 → 分配可用空间给组件
一个页面的正确写法:外层媒体查询定页面节奏,组件内部容器查询定自身形态。反例是所有响应式规则都堆在媒体查询里,导致组件搬到新位置必须重新加断点。
@media (max-width: 720px) {
.layout { grid-template-columns: 1fr; } /* 页面级 */
}
@container nav (min-width: 560px) {
.nav__links { display: flex; } /* 组件级,与视口无关 */
}
六、可访问性与降级
容器查询本身不涉及可访问性风险,但有两点要注意:
- 不要用
display: none隐藏仍在 Tab 序列的元素。若靠容器查询隐藏表单/导航项,用visibility+ 移出 Tab 或不渲染,别只靠display: none后留着aria-*; - 降级策略:不支持容器查询的浏览器(较老版本)应拿到"单列/紧凑"默认形态,即把最保守的形态写成不依赖容器查询的基线样式。
.card { grid-template-columns: 1fr; } /* 基线:无容器查询也正确 */
@container (min-width: 280px) { /* 增强 */ }
七、性能与调试
containment 是性能助力:容器内布局变化不向外传播,减少 relayout 范围。但有两个陷阱:
- 容器嵌套过深 + 每层
container-type: size容易形成相互尺寸依赖,浏览器会跳过计算(表现为查询不生效),排查方式是把中间层的size换成inline-size; - 断点在 DevTools 里不会像媒体查询那样高亮。Chrome 的 Containers 面板(Ctrl/Cmd+Shift+P 搜 "Containers")可以实时看每个容器的尺寸与命中的查询。
小结
容器查询的价值不是"更强的媒体查询",而是把响应式责任交还给组件。规则:页面节律用媒体查询,组件形态用容器查询,空间分配用栅格;container-type 默认 inline-size,多容器一律命名;基线样式必须在不支持的环境下依然成立;绝对定位与 contain: paint 的副作用要用 DevTools 的 Containers 面板确认。做到这几条,设计系统才真正可以"组件跟着空间走",而不是每个页面重复发明断点。
评论(0)
登录 后参与评论。