容器查询落地三年后:响应式的单位从「屏幕」变成了「组件」
容器查询落地三年后:响应式的单位从「屏幕」变成了「组件」
@media问的是「窗口多宽」,@container问的是「我被放进了多大的地方」。
这个差看着只是换了个参照物,实际把组件库的设计前提整个换掉了。
一、@media 的根本缺陷:组件不知道自己在哪
一个卡片组件,在首页三列网格里只有 320px,在详情页侧栏只有 280px,在文章正文里可能占满 900px。
用媒体查询写它,只有一个办法:把「在哪种布局里」这件事从组件内部搬到调用方。于是你得到一堆这种东西:
1 | .card { /* 默认窄屏样式 */ } |
每加一个使用场景,就要加一条覆盖规则,而且覆盖规则写在组件外面——组件的样式依赖调用它的上下文,这本身就是耦合。
二、换参照物之后
容器查询的做法是让父级声明「我可以被查询」:
1 | .card-grid { |
组件只关心「我拿到多宽」,不再关心「窗口多宽」。同一份组件代码,扔进网格、侧栏、正文,各自按可用宽度变形,调用方不用写一行覆盖。
这是三年下来最重要的观念转变:响应式的断点属于组件,不属于页面。
三、container-type 的两个陷阱
第一个:inline-size 会建立包含块,影响绝对定位与固定尺寸。
它让容器成为其子元素的包含块(containment),所以容器内部的 position: fixed 不再相对视口,height: 100% 的参照也变了。给普通卡片加没问题,给一个本来靠 fixed 做浮层的组件加,就会莫名错位。
第二个:查询的是内容盒子,不是想象中那个宽度。
inline-size 按行内方向计算,滚动条、padding 都参与。如果容器是 display: inline,它根本不是一个合格容器——必须是有确定宽度的块级或 flex/grid 项。
四、真正省事的不是断点,是容器单位
比 @container 更少被提的是容器单位:cqw、cqh、cqi、cqb、cqmin、cqmax。
1 | .card__title { |
以前要实现「字号随卡片宽度缩放」,要么用 vw(错,因为跟视口而非容器),要么用 JS 算 ResizeObserver 再写 CSS 变量。现在一行 CSS 解决,而且不触发重排。
这条比断点更值得先上,因为它是纯粹替换:把 vw 换成 cqi,把 JS 的 ResizeObserver 字号逻辑删掉,风险几乎为零,收益立刻可见。
五、@container style() 与「组件变体」
style queries 允许查询父级的自定义属性值:
1 | @container style(--theme: compact) { |
它让「变体」从 props 传递变成继承传递:外层容器打一个标记,内部所有子组件自动响应,不用一层层透传 class。
代价是可读性。当样式可以因为某个祖先上的一个自定义属性而改变,你在 DevTools 里看到一个元素样式不对时,得往上翻才知道是谁设了 --theme。从”显式覆盖”到”隐式继承”,换来的灵活性要用可调试性付账。
六、什么时候仍然用 @media
容器查询不取代媒体查询,它接管的是另一类问题:
| 场景 | 用什么 |
|---|---|
| 组件在不同容器里变形 | @container |
| 全站布局切换(单栏 / 双栏 / 三栏) | @media |
| 字号随组件宽度缩放 | cqi + clamp() |
| 打印、深浅色、减少动效 | @media(偏好类) |
| 组件在不同主题下微调 | @container style() |
判据一句话:你要判断的是「容器多大」还是「设备/环境如何」。前者容器查询,后者媒体查询。
七、迁移建议
按性价比排序,先做这三件:
- **把
vw相关的字号、间距改成cqi**。纯替换,不涉及结构。 - 挑一个真正的复用组件(卡片、媒体对象、列表项)改成容器查询,删掉它周围所有
.sidebar .card式的覆盖规则。这一步最能暴露原来的耦合有多深。 - 容器命名,别用匿名容器。
container-name是给三个月后的自己留的路标;匿名容器在嵌套场景里会查询到最近的那个,出问题时极难定位。
八、一句话总结
容器查询不是「媒体查询的新语法」,它把「组件应该长什么样」这个决定从页面层收回了组件层。
做过这一步的组件库,代码会变少——因为不再需要为每个使用场景写例外。当你发现自己在写「某容器下的某组件」,那通常就是容器查询该上场的地方。
评论