容器查询、:has()、级联层、视图过渡、锚点定位——这些能力单独看是又一个新属性,合在一起却改变了样式表的组织方式。本文只讲能在生产落地的部分:每项解决什么老问题、代码怎么写、兼容性红线在哪,并给出按收益风险排序的落地顺序。
过去几年 CSS 的更新密度,是这门语言诞生以来最高的。容器查询、:has()、级联层、视图过渡、锚点定位……这些能力单独看是”又一个新属性”,合在一起却改变了样式表的组织方式:组件终于可以真正独立,动画终于可以真正声明式。
本文不罗列规范,只讲能在生产里落地的部分:每项能力解决什么老问题、代码怎么写、以及必须知道的兼容性红线。

一、容器查询:组件终于能自己决定长什么样
媒体查询(@media)问的是”视口有多宽”,但组件真正关心的是“我被放进了多大的空间”。同一个卡片组件,塞进 320px 的侧栏和塞进 900px 的主内容区,本来就应该长成两个样子——在容器查询之前,这件事只能靠 JS 测量或者给组件传 size 属性硬凑。
.card-container {
container-type: inline-size;
}
.card { /* 窄容器:纵向堆叠 */ }
@container (min-width: 480px) {
.card { /* 宽容器:左右布局 */ }
}
关键点是 container-type: inline-size,它把这个元素声明为一个”可查询的容器”。注意副作用:声明了 inline-size 的元素,其行内尺寸不再受内容撑开影响,布局上要额外确认一遍。
落地建议:优先在真正会被复用到不同位置的组件上用(卡片、列表项、工具栏),而不是全站铺开。铺开之后调试成本会明显上升,因为你得一级一级往上找是哪个容器在生效。
二、:has():缺失了二十年的父选择器
“如果这张卡片里有图片,就换一套内边距”——这类需求以前只能靠在模板层加 class。:has() 把它变回纯 CSS:
.card:has(img) {
padding-top: 0;
}
/* 表单校验失败的字段,高亮整个表单组 */
.form-group:has(:invalid) {
border-color: #d94f5c;
}
它的价值不只是少写 JS,而是把”结构判断”这件事放回了它该在的地方。模板不用再为了样式去维护一堆状态 class。
需要注意的性能认知:早期的担忧是 :has() 会不会很慢。实际在现代引擎里,限定作用域的写法(如 .card:has(img))开销可接受;真正要避免的是无约束的全局写法,比如 :has(*) 这类把整棵树都纳入匹配的模式。
三、级联层:把 !important 战争提前终结
大型项目样式失控的典型路径是:第三方库样式盖不住 → 加 !important → 别人再加一层 → 谁也不敢删。@layer 提供的是在层与层之间预先定好优先级的机制:
@layer reset, vendor, components, utilities;
@layer vendor { @import url("some-lib.css"); }
@layer components { .btn { /* ... */ } }
@layer utilities { .mt-4 { margin-top: 1rem; } }
声明顺序即优先级顺序:后声明的层压过先声明的层,且这个优先级高于选择器特异性。这意味着 utilities 层里的一个单类选择器,可以稳定压过 components 层里很长的后代选择器——再也不用靠堆类名长度取胜。
迁移成本很低:把现有的样式分块包进 @layer 即可,不需要改任何选择器。建议在新项目里第一天就建好层结构,老项目则可以在引入新模块时逐步收敛。
四、视图过渡:声明式的页面切换动画
单页应用里的路由切换动画,以前意味着一堆状态管理和 requestAnimationFrame。视图过渡 API 把这件事变成了浏览器原生能力:
function navigate(to) {
if (!document.startViewTransition) {
updateDOM(to) // 不支持就老老实实直接切
return
}
document.startViewTransition(() => updateDOM(to))
}
浏览器会自动截取切换前后的两帧做交叉淡入,元素只要标记了 view-transition-name,就能在两个状态之间自动做位置和尺寸补间——列表项点开变成详情页大图这种效果,几行 CSS 就能实现。
两条红线:一是必须做特性检测,不支持的环境要能正常降级;二是记得照顾 prefers-reduced-motion,对前庭功能敏感的用户直接关掉过渡。
五、还有几项值得排进技术雷达
subgrid:让子元素的网格对齐父网格。做”多行卡片左右列对齐”这种需求时,终于不用给每个卡片塞死高度;- 锚点定位(Anchor Positioning):把浮层绑定到触发元素,不用再手动算坐标。Tooltip、下拉菜单的味道很对,但兼容性还在铺,建议先做渐进增强;
oklch()与color-mix(): perceptual 均匀的色彩空间,做主题色阶时比 HSL 靠谱得多,深浅色模式下的对比度也更可预期;- 滚动驱动动画:把动画进度绑到滚动位置,纯 CSS 实现,不再需要 scroll 事件监听。阅读进度条、滚动渐显这类效果的性价比极高。
六、落地顺序建议
不要把新特性一次性全上。按”收益 / 风险”排,我的建议顺序是:
@layer——几乎零风险,立刻解决样式失控,收益立竿见影;- 容器查询——解决真实痛点,且不支持时会优雅退回基础布局;
:has()——能删掉一批模板层状态逻辑,写法收敛后很好维护;- 视图过渡——体验提升最直观,但一定要写降级分支;
- 锚点定位 / 滚动驱动动画——先在非关键路径上试,等覆盖面更广再进主流程。
七、小结
这一轮 CSS 更新的共同主题,是把以前必须写 JS 才能做的事,收回到样式层。容器查询收走了尺寸测量,:has() 收走了结构判断,视图过渡收走了动画编排,@layer 收走了优先级管理。
对工程的实际影响是:组件的自洽性变强了。一个组件能不能”放哪都长对”,以前取决于使用方传没传对参数,现在取决于它自己写没写对容器查询。这个转变值得重新审视一遍现有的组件库。
#CSS#前端#容器查询#响应式设计

