媒体查询是按视口写样式,容器查询是按父容器写样式。听起来只是换了个参照物,实际用起来会改变组件的组织方式。我最近把一个后台的卡片列表从媒体查询迁到容器查询,代码少了一截,组件也终于能独立复用了。
从视口到容器
问题出在同一个卡片会出现在侧栏、主区域和弹窗里。视口宽的时候,侧栏里的卡片也被判定为宽屏,于是排版挤成一团。用媒体查询只能在父级加一堆覆盖规则。
容器查询的断点要按组件设计,不是按页面。我一般只留两三个,多了维护成本会上去,也很难说清每个断点为什么存在。
容器查询需要先声明容器。
.card-list {
container-type: inline-size;
container-name: cards;
}
@container cards (min-width: 480px) {
.card {
display: grid;
grid-template-columns: 96px 1fr;
gap: 16px;
}
}
@container cards (min-width: 720px) {
.card__meta {
display: flex;
justify-content: space-between;
}
}
container-type: inline-size 让容器在行内方向上可被查询,同时建立了包含块。它有一个副作用:容器的高度不再由内容撑开参与外部布局计算,涉及绝对定位和 aspect-ratio 的地方要重新检查。
容器单位
容器查询单位是 cqw、cqh、cqi、cqb 这一组,分别对应容器宽高和行内块方向。我在卡片标题上用 cqi 做字号,让标题随卡片宽度缩放。
.card__title {
font-size: clamp(1rem, 4cqi, 1.5rem);
line-height: 1.3;
}
配合 clamp 才不会在小卡片里小到看不清。纯用 cqi 的写法我见过,字小到 8px,那是把响应式理解成了等比缩放。
顺便说一句,cqb 在垂直书写模式下才有实际意义,横排中文项目基本用不到,但写通用组件时最好知道它存在。
什么时候仍然用媒体查询
- 页面级布局,比如侧栏折叠、导航形态
- 打印样式和 prefers-reduced-motion 这类用户偏好
- 主题切换,比如根据 prefers-color-scheme 改配色
- 需要跟随视口而不是组件宽度的场景
判断标准很简单:这个样式变化的触发因素是窗口,还是组件所在的容器。前者用媒体查询,后者用容器查询。
迁移时的注意点
容器查询不能查询自身。一个元素声明了 container-type,它的 @container 规则只对后代生效,自己不受影响。这经常让人写出不生效的样式。
另外,容器查询的兼容性已经不错,但老项目里可能有基于 JS 的尺寸监听,比如 ResizeObserver 加 class 切换。迁移时不要一次全删,先让两套并存,确认视觉一致再清理。我吃过一次亏,某个断点在容器查询下触发的时机比原来早,图表重绘了两次,排查了半天才发现是容器宽度包含了 padding。
调试时可以直接在开发者工具里选中容器元素,看它当前的尺寸和命中的规则,比改一行刷新一次快得多。