CSS 新特性
问题
近几年 CSS 有哪些重要的新特性?@layer、:has()、CSS Nesting、@scope、Subgrid 等怎么用?
答案
概览
近年来 CSS 发展迅速,以下是按重要性和实用性排序的新特性:
| 特性 | 状态 | 兼容性 |
|---|---|---|
| CSS Nesting | 稳定 | Chrome 120+、Firefox 117+、Safari 17.2+ |
:has() 父选择器 | 稳定 | Chrome 105+、Firefox 121+、Safari 15.4+ |
@layer 级联层 | 稳定 | Chrome 99+、Firefox 97+、Safari 15.4+ |
| Container Queries | 稳定 | Chrome 105+、Firefox 110+、Safari 16+ |
@scope | 较新 | Chrome 118+、Safari 17.4+(Firefox 暂不支持) |
| Subgrid | 稳定 | Chrome 117+、Firefox 71+、Safari 16+ |
@property | 稳定 | Chrome 85+、Firefox 128+、Safari 15.4+ |
color-mix() | 稳定 | Chrome 111+、Firefox 113+、Safari 16.2+ |
@starting-style | 较新 | Chrome 117+、Safari 17.5+(Firefox 暂不支持) |
| View Transitions | 较新 | Chrome 111+(Firefox、Safari 有限) |
| Popover API | 较新 | Chrome 114+、Firefox 125+、Safari 17+ |
| Anchor Positioning | 最新 | Chrome 125+ |
CSS Nesting(原生嵌套)
CSS 终于原生支持嵌套,不再需要预处理器:
.card {
padding: 16px;
border-radius: 8px;
/* 嵌套子选择器 */
.title {
font-size: 20px;
font-weight: bold;
}
.description {
color: #666;
margin-top: 8px;
}
/* 伪类 */
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
/* 伪元素 */
&::before {
content: '';
}
/* 媒体查询也可以嵌套 */
@media (min-width: 768px) {
padding: 24px;
}
}
- 嵌套选择器如果不是以符号开头(如
.class、#id、&),浏览器会自动添加& & .title和.title效果相同(等价于.card .title)- 需要
&的场景:伪类&:hover、伪元素&::after、父选择器引用.dark &
:has() 父选择器
CSS 中第一个真正的"向上"选择器,可以根据子元素状态选择父元素:
/* 包含图片的卡片 */
.card:has(img) {
padding: 0;
}
/* 包含 .error 的表单组 */
.form-group:has(.error) {
border-color: red;
}
/* checkbox 选中时改变 label 样式 */
label:has(input:checked) {
background: #e0f0ff;
font-weight: bold;
}
/* 后面紧跟 p 的 h2(兄弟选择) */
h2:has(+ p) {
margin-bottom: 0;
}
/* 不包含某元素 */
.card:not(:has(img)) {
min-height: 200px;
}
/* 量选择器:超过 3 个子元素时 */
ul:has(li:nth-child(4)) {
columns: 2; /* 超过 3 个项目时分两列 */
}
:has() 能实现以前只能用 JS 做的很多效果:
- 表单验证状态反馈
- 空状态处理
- 响应式组件(根据内容决定布局)
- 条件样式(根据兄弟元素状态变化)
@layer 级联层
@layer 控制样式的级联优先级,解决第三方库样式覆盖困难的问题:
/* 声明层级顺序(越后面优先级越高) */
@layer reset, base, components, utilities;
/* reset 层(最低优先级) */
@layer reset {
* { margin: 0; padding: 0; box-sizing: border-box; }
}
/* base 层 */
@layer base {
body { font-family: system-ui; line-height: 1.5; }
a { color: #3b82f6; }
}
/* components 层 */
@layer components {
.button {
padding: 8px 16px;
border-radius: 4px;
}
}
/* utilities 层(最高优先级) */
@layer utilities {
.hidden { display: none; }
.flex { display: flex; }
}
关键规则:
- 层内的选择器优先级仍然正常计算
- 层之间的优先级由
@layer声明顺序决定 - 未分层的样式优先级高于所有层内样式
/* 实际应用:确保自定义样式覆盖第三方库 */
@layer third-party, custom;
@layer third-party {
@import url('library.css');
}
@layer custom {
/* 即使选择器优先级低,也能覆盖 third-party 层 */
.button { background: red; }
}
@scope
作用域限定,将样式限制在特定 DOM 子树中:
@scope (.card) {
/* 只在 .card 内部生效 */
.title { font-size: 20px; }
.description { color: #666; }
}
/* 设置上下边界(donut scope) */
@scope (.card) to (.card-footer) {
/* 在 .card 内、.card-footer 外生效 */
p { color: #333; }
}
<div class="card">
<p>这段会被 @scope 样式影响</p>
<div class="card-footer">
<p>这段不会被影响(在 to 边界外)</p>
</div>
</div>
Subgrid
子元素继承父 Grid 的轨道定义,实现跨层级对齐:
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.card {
display: grid;
/* 继承父级的列轨道 */
grid-template-columns: subgrid;
grid-column: span 3; /* 跨越父级 3 列 */
}
解决场景:多张卡片的标题、内容、按钮需要在同一行对齐。
@property 注册自定义属性
让浏览器理解 CSS 变量的类型,从而支持动画:
@property --gradient-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.box {
--gradient-angle: 0deg;
background: linear-gradient(var(--gradient-angle), #f00, #00f);
transition: --gradient-angle 0.5s;
}
.box:hover {
--gradient-angle: 180deg;
}
syntax 支持的类型:<number>、<length>、<percentage>、<color>、<angle>、<image> 等。
color-mix()
混合两种颜色:
.box {
/* 50% 蓝色 + 50% 红色 */
background: color-mix(in srgb, blue, red);
/* 80% 主色 + 20% 白色(更浅) */
background: color-mix(in srgb, var(--primary) 80%, white);
/* 动态生成 hover 颜色 */
&:hover {
background: color-mix(in srgb, var(--primary), black 20%);
}
}
替代 Sass 的 darken()、lighten() 和 mix() 函数,且是运行时的。
@starting-style
定义元素从 display: none 变为显示时的初始样式,实现入场动画:
dialog {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s, transform 0.3s, display 0.3s allow-discrete;
}
/* 关闭时的状态 */
dialog:not([open]) {
opacity: 0;
transform: translateY(-20px);
display: none;
}
/* 打开时的入场起点 */
@starting-style {
dialog[open] {
opacity: 0;
transform: translateY(20px);
}
}
View Transitions
页面或视图切换动画的原生 API:
function navigate(url: string): void {
if (!document.startViewTransition) {
updateDOM(url);
return;
}
document.startViewTransition(() => {
updateDOM(url);
});
}
::view-transition-old(root) {
animation: fade-out 0.3s ease-out;
}
::view-transition-new(root) {
animation: fade-in 0.3s ease-in;
}
/* 为特定元素命名 */
.hero-image {
view-transition-name: hero;
}
Popover API
原生弹出层,自带顶层渲染和关闭行为:
<button popovertarget="menu">打开菜单</button>
<div id="menu" popover>
<p>弹出内容,点击外部自动关闭</p>
</div>
[popover] {
/* 在 top layer 渲染,无需 z-index */
padding: 16px;
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
/* 配合 @starting-style 实现动画 */
[popover]:popover-open {
opacity: 1;
transform: scale(1);
}
常见面试问题
Q1: CSS :has() 有什么用?能举例吗?
答案:
:has() 是父选择器,根据子元素或后续兄弟的存在/状态选择当前元素:
/* 表单验证 */
.input-group:has(input:invalid) { border-color: red; }
.input-group:has(input:valid) { border-color: green; }
/* 空状态 */
.list:not(:has(li)) { display: none; } /* 没有 li 时隐藏列表 */
.list:not(:has(li)) + .empty-state { display: block; }
本质上弥补了 CSS 只能"向下"选择的缺陷。
Q2: @layer 解决什么问题?
答案:
解决第三方库样式难以覆盖的问题。传统方式要用更高优先级的选择器或 !important;@layer 让你定义样式层级,不管选择器优先级如何,层级顺序决定最终胜出:
@layer third-party, custom;
/* custom 层的样式永远覆盖 third-party 层 */
Tailwind CSS v4 就使用 @layer 组织样式层级。
Q3: CSS Nesting 和 Sass 嵌套有什么区别?
答案:
| 特性 | CSS Nesting | Sass 嵌套 |
|---|---|---|
| 运行时 | 浏览器原生 | 编译时 |
& 的用法 | 自动推断,伪类/伪元素需要 & | 始终用 & 引用父选择器 |
@media 嵌套 | ✅ 支持 | ✅ 支持 |
| 兼容性 | 现代浏览器 | 所有浏览器(编译后) |
主要区别:CSS 原生嵌套器中,如果嵌套选择器是类名或元素名开头,不需要写 &(浏览器自动添加)。
Q4: @property 有什么用?
答案:
@property 让浏览器理解 CSS 变量的类型,核心用途是让 CSS 变量可以做动画:
@property --color {
syntax: '<color>';
inherits: false;
initial-value: red;
}
.box {
background: var(--color);
transition: --color 0.3s;
}
.box:hover { --color: blue; } /* 颜色会平滑过渡! */
还可以实现渐变动画、计数器动画等以前纯 CSS 无法实现的效果。
Q5: color-mix() 是什么?有什么用处?
答案:
color-mix() 在指定色彩空间中混合两种颜色:
/* 动态生成浅色/深色变体 */
--primary: #3b82f6;
--primary-light: color-mix(in srgb, var(--primary), white 30%);
--primary-dark: color-mix(in srgb, var(--primary), black 20%);
替代 Sass 的 lighten()/darken(),且是运行时的——可以配合 CSS 变量动态计算。
Q6: View Transitions 是什么?
答案:
View Transitions API 提供了原生的页面/视图过渡动画能力。调用 document.startViewTransition() 后,浏览器会:
- 截取当前页面快照(old state)
- 执行 DOM 更新
- 截取新页面快照(new state)
- 在两个快照之间执行过渡动画
可以实现类似 Native App 的页面切换效果,且不需要额外的动画库。
Q7: 这些新特性的兼容性怎么样?能用在生产环境吗?
答案:
| 特性 | 2024 年生产可用 |
|---|---|
:has() | ✅ 可用 |
| CSS Nesting | ✅ 可用(配合 PostCSS 降级) |
@layer | ✅ 可用 |
| Container Queries | ✅ 可用 |
color-mix() | ✅ 可用 |
@property | ✅ 可用 |
| Subgrid | ✅ 可用 |
@scope | ⚠️ 谨慎(Firefox 不支持) |
| View Transitions | ⚠️ 渐进增强使用 |
| Anchor Positioning | ❌ 等待更广泛支持 |
建议::has()、@layer、Nesting、Container Queries 可放心使用;@scope、View Transitions 做渐进增强。