问题场景
以前做响应式,只能靠"视口宽度"(media query)------但真正的问题往往是"这个组件在它父容器里放不下",而不是"屏幕变窄了"。还有:想"根据父元素有没有某类子元素来调样式"(如验证失败提示),过去必须写 JS。
现代 CSS 给出了一批颠覆性 新特性,能干掉大量 JS 和 hack:容器查询(container query)、:has() 父选择器、@scope 作用域、子网格(subgrid)。掌握它们,你的组件库能真正复用、样式更声明式、代码更少。
原理深入
1. container query:按"容器"而不是"视口"响应
@container 让组件根据它自己所在容器的尺寸来调整,而不是整屏宽度------这才是真正的"组件级响应式",让组件能被塞进任何地方都能自适应。
css
.card {
container-type: inline-size; /* 声明该元素是一个查询容器(按内联轴) */
}
@container (min-width: 400px) {
.card { display: flex; } /* 容器 ≥400px 时切换布局 */
}
关键点:
container-type: inline-size把元素变成"查询容器"(必须声明才能被查询)。- 可给容器命名:
container-name: sidebar,用@container sidebar (min-width:...)精确匹配。 - 可嵌套:容器本身也可以是查询容器。
- 相比 media query:media query 只能问"整个视口多宽",container query 能问"我这个组件所在位置多宽"------组件真正可复用。
2. :has():终于能选"有某个子元素的父元素"
:has() 是"父选择器 / 关系选择器",依子元素/后继调整父或兄弟样式,告别 JS hack:
css
/* 表单:输入框有值时,label 变蓝 */
.input-wrap:has(input:focus) { border-color: blue; }
/* 卡片图上有图片时才显示标题背景 */
.card:has(img) .title { background: rgba(0,0,0,.5); }
/* 导航:有子菜单的项显示箭头 */
li:has(> ul)::after { content: '▾'; }
/* 选择前一个兄弟(没有 :previous-sibling,用 :has 实现) */
h2:has(+ p) { margin-bottom: 8px; } /* 紧跟 <p> 的 h2 */
常见用途:
- 有图片/图标的卡片才加某种样式。
- 有校验错误的字段才标红(
:has(:invalid))。 - 实现过去需要 JS 的"父元素根据子元素状态变化"。
3. @scope:CSS 作用域,简化嵌套组件样式
@scope 限定样式只作用于某段 DOM,类似"局部作用域"(避免类名过长嵌套):
css
@scope (.card) {
.title { font-weight: 700; } /* 只作用于 .card 内的 .title */
p { line-height: 1.5; } /* 只作用于 .card 内的 p */
}
相当于 .card .title / .card p,但更清晰的"作用域边界"表达,还能配合 :scope 指代根。
好处:
- 不用再写超长的 BEM 类名或深层选择器(
.card__title--large)。 - 清晰表达"这段样式的边界"。
- 比
@layer(控制优先级)更侧重"控制作用范围"。
4. subgrid:让子元素的网格对齐父网格
grid-template-columns 默认子网格独立。subgrid 让内层网格沿用外层网格的列/行轨道,实现完美对齐的复杂布局(表格/卡片墙):
css
.outer {
display: grid;
grid-template-columns: repeat(3, 1fr);
}
.inner {
grid-column: 1 / -1; /* 横跨全部列 */
display: grid;
grid-template-columns: subgrid; /* 沿用外层的 3 列,对齐内部元素 */
}
典型用途:列表/表格里,多个子项的行高、列宽要和父网格精确对齐,而不需要自己再算。
5. 其他值得关注的新特性
css
/* aspect-ratio:固定宽高比(替代 padding-top hack) */
.video { aspect-ratio: 16 / 9; width: 100%; }
/* 视口单位增强:svh/lvh/dvh 适配移动端动态栏 */
.hero { height: 100dvh; } /* 动态视口高 */
/* 滚动驱动动画(scroll-driven animations) */
@keyframes reveal { from { opacity: 0; } to { opacity: 1; } }
.reveal { animation: reveal linear both; animation-timeline: view(); }
/* color-mix:颜色混合主题色 */
.btn { background: color-mix(in srgb, var(--primary) 80%, white); }
实战代码:一个"响应式数据卡片"用容器查询 + :has
html
<div class="card-panel">
<div class="widget">
<h3 class="widget__title">报表</h3>
<div class="widget__chart">📊</div>
<p class="widget__desc">本月营收 120 万</p>
</div>
</div>
css
.card-panel {
container-type: inline-size;
container-name: panel;
}
.widget { display: block; }
/* 容器变宽,切换到横向布局 ------ 组件级响应式 */
@container panel (min-width: 420px) {
.widget { display: grid; grid-template-columns: 1fr 2fr; align-items: center; }
}
/* 有图表时给描述加底色(:has) */
.widget:has(.widget__chart) .widget__desc { background: #f5f7fa; padding: 8px; }
效果:这个
.widget组件无论被放进侧边栏还是主区,都能根据自己容器宽度自适应布局,无需知道外部视口大小。
要点总结
- container query :按容器宽度响应,构建真·可复用组件;
container-type: inline-size声明容器。 :has():父/关系选择器,干掉大量"根据子元素调父样式"的 JS。@scope:CSS 局部作用域,简化嵌套组件样式,替代深层/BEM 类名。subgrid:子网格沿用父网格轨道,复杂对齐不再手写。- 其他:
aspect-ratio、dvh/svh/lvh、滚动驱动动画、color-mix都值得跟进。 - 兼容性:现代浏览器(2023-2024)主流特性已支持,老旧项目按需渐进增强。
- 应用价值:组件库可复用性、减少 JS hack、代码更声明式更清晰。
一句话:container query 让"组件自己响应"、
:has()让"父元素看子元素"、@scope让"样式有边界"、subgrid 让"网格能穿透对齐"------现代 CSS 正在把过去要写 JS 的事,变回纯 CSS 的声明式表达。