别再写死断点了:容器查询与 :has() 带来的布局革命
做组件库最尴尬的事:同一个卡片,放在侧边栏很挤、放在主区很空。过去只能靠媒体查询硬切,但媒体查询盯的是视口,不是组件真正的大小。好消息是,现在可以用容器查询和 :has() 解决。
媒体查询的死穴
/* 旧思路:根据整个屏幕宽度决定卡片布局 */
@media (min-width: 1024px) {
.card { display: flex; }
}
问题在于:一张卡片在 1024px 的页面里可能位于 300px 的侧栏,也可能位于 700px 的主区。视口相同,可用空间却完全不同——媒体查询无能为力。
容器查询:组件按"自己的容器"响应
先声明"我是可被查询的容器",再基于容器宽度写样式:
.card-wrapper {
container-type: inline-size; /* 声明容器,可按宽度查询 */
}
@container (min-width: 480px) {
.card {
display: flex;
gap: 16px;
}
.card__thumb {
width: 120px;
flex-shrink: 0;
}
}
现在卡片的行为只取决于它实际被放进多宽的容器——放在窄侧栏就纵向堆叠,放进宽主区就横向排布。组件从此"到哪都能用",不再依赖页面布局。
| 维度 | 媒体查询 @media |
容器查询 @container |
|---|---|---|
| 响应对象 | 视口 / 设备 | 最近的容器元素 |
| 复用性 | 低(绑定页面) | 高(组件自包含) |
| 典型用途 | 整页断点 | 卡片、列表、组件内部 |
:has():CSS 第一次能"向上选"
过去 CSS 只能选后代、兄弟,没法根据"有没有某个子元素"来给父级加样式。:has() 补上了这个缺口:
/* 只要组内包含图片,就切换成图文布局 */
.media-group:has(img) {
display: grid;
grid-template-columns: auto 1fr;
}
/* 表单里只要有一个必填项没填,给整张卡片描红边 */
.form-card:has(input:invalid) {
border-color: #e5484d;
}
:has() 本质是"父级根据子级状态做反应",这在以前必须靠 JS 监听才能做到。
两者合体:自适应 + 自感知
容器查询解决"空间够不够",:has() 解决"内容是什么",组合起来组件就真正智能了:
.product {
container-type: inline-size;
}
/* 空间够宽,且带评分徽章时,才展示横向图文布局 */
.product:has(.badge) {
@container (min-width: 420px) {
display: flex;
align-items: center;
}
}
这样一个商品卡:窄容器里自动堆叠;宽容器里自动横排;一旦带促销徽章,还会额外切换成强调样式——全程零 JS。
兼容性怎么看
- 容器查询:现代浏览器(Chrome/Edge/Firefox/Safari 近两年版本)均已支持,生产可用;
:has():同样主流已支持,连 Safari 都早早跟进。
老浏览器兜底很简单:把新写法当成"渐进增强",默认给一个基础布局,支持的新浏览器再叠加精致布局。即使不支持,页面也只是"没那么自适应",不会崩。
小结
两个特性把"响应式"的重心从页面挪回了组件:
- 容器查询让组件按自身容器宽度响应,彻底摆脱视口束缚;
:has()让父级能根据子级是否存在 / 是否某状态来变化,补足了 CSS 最后一块短板;- 合起来,很多原本要写 JS 才能做的"智能布局",现在纯 CSS 就能搞定,且性能更好。
做组件库或设计系统时,这俩值得第一时间用上。