跳到主要内容

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 终于原生支持嵌套,不再需要预处理器:

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 中第一个真正的"向上"选择器,可以根据子元素状态选择父元素:

:has() 用法
/* 包含图片的卡片 */
.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() 的实际应用

:has() 能实现以前只能用 JS 做的很多效果:

  • 表单验证状态反馈
  • 空状态处理
  • 响应式组件(根据内容决定布局)
  • 条件样式(根据兄弟元素状态变化)

@layer 级联层

@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 基本用法
@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 的轨道定义,实现跨层级对齐

Subgrid 用法
.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 用法
@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 变为显示时的初始样式,实现入场动画:

@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:

SPA View Transition
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 NestingSass 嵌套
运行时浏览器原生编译时
& 的用法自动推断,伪类/伪元素需要 &始终用 & 引用父选择器
@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() 后,浏览器会:

  1. 截取当前页面快照(old state)
  2. 执行 DOM 更新
  3. 截取新页面快照(new state)
  4. 在两个快照之间执行过渡动画

可以实现类似 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 做渐进增强。

相关链接