{T}

现代 CSS 特性

背景与动机

近年来,CSS 规范引入了大量革命性的新特性,这些特性显著提升了 CSS 的表达能力和开发效率,正在彻底改变前端开发的编写方式。

为什么需要现代 CSS 特性?

传统 CSS 开发面临以下挑战:

  1. 样式组织混乱:依赖预处理器(Sass/Less)实现嵌套和变量
  2. 优先级冲突:样式覆盖难以管理,!important 滥用
  3. 响应式局限:只能基于视口尺寸,无法实现组件级响应式
  4. 动画能力有限:复杂动画需要 JavaScript,性能不佳
  5. 选择器能力不足:无法根据子元素状态选择父元素

现代 CSS 特性解决了这些问题:

  • 原生嵌套:无需预处理器即可实现层级化样式
  • 显式层叠控制:通过 @layer 精确管理样式优先级
  • 容器查询:基于组件容器尺寸实现真正的响应式设计
  • 强大选择器:has() 实现"父选择器"功能
  • 高级动画:View Transitions、滚动驱动动画等
  • 类型化变量@property 让 CSS 变量具备类型和动画能力

现代 CSS 特性全景图

图表渲染中…

浏览器支持概览

特性ChromeFirefoxSafari状态
CSS 嵌套120+117+17.2+✅ 稳定
@layer99+97+15.4+✅ 稳定
:has()105+121+15.4+✅ 稳定
容器查询105+110+16+✅ 稳定
@property85+128+16.4+✅ 稳定
View Transitions111+18+⚠️ 部分支持
样式查询111+⚠️ 实验性
@scope118+17.4+⚠️ 部分支持
滚动驱动动画115+⚠️ 实验性

核心概念

CSS 特性演进时间线

code
CSS 演进历程:

2018 ─── @property (Chrome 85)
         │
2020 ─── @layer (Chrome 99)
         │
2021 ─── 容器查询 (Chrome 105)
         │
2022 ─── :has() (Chrome 105)
         │
2023 ─── CSS 嵌套 (Chrome 120)
         │
2024 ─── View Transitions (Chrome 111)
         │
         └── 滚动驱动动画 (Chrome 115)

深入原理

CSS 嵌套(Nesting)工作原理

CSS 嵌套允许在规则内部编写子规则,无需预处理器即可实现层级化的样式组织。

编译原理

浏览器在解析嵌套 CSS 时,会将其转换为标准的选择器:

css
/* 嵌套写法 */
.card {
  padding: 1rem;
  
  & .title {
    font-size: 1.5rem;
  }
}

/* 等价于 */
.card {
  padding: 1rem;
}

.card .title {
  font-size: 1.5rem;
}

& 符号的作用

& 代表父选择器,在嵌套规则中必须显式使用:

css
.card {
  /* 后代选择器 */
  & .title { }          /* .card .title */
  
  /* 父选择器在前 */
  .wrapper & { }        /* .wrapper .card */
  
  /* BEM 修饰符 */
  &--featured { }       /* .card--featured */
  
  /* BEM 元素 */
  &__title { }          /* .card__title */
  
  /* 复合选择器 */
  .section & &--large { } /* .section .card.card--large */
}

嵌套 @规则

嵌套不仅支持选择器,还支持 @规则:

css
.component {
  font-size: 1rem;

  /* 嵌套媒体查询 */
  @media (min-width: 768px) {
    font-size: 1.125rem;
  }

  /* 嵌套容器查询 */
  @container (min-width: 400px) {
    display: flex;
  }

  /* 嵌套特性检测 */
  @supports (backdrop-filter: blur(10px)) {
    backdrop-filter: blur(10px);
  }
}

CSS 层叠层(@layer)原理

@layer 允许开发者显式控制 CSS 样式的层叠顺序,解决样式优先级冲突问题。

层叠顺序规则

code
CSS 层叠优先级(从低到高):

┌─────────────────────────────────────┐
│  @layer base                        │  ← 最低优先级
├─────────────────────────────────────┤
│  @layer components                  │
├─────────────────────────────────────┤
│  @layer utilities                   │
├─────────────────────────────────────┤
│  无 @layer 的样式                    │  ← 最高优先级
├─────────────────────────────────────┤
│  !important 声明                     │  ← 最高(反向)
└─────────────────────────────────────┘

层叠计算流程

code
样式优先级计算:

1. 确定样式所属层级
   └─ @layer 内 → 层优先级
   └─ @layer 外 → 高于所有层

2. 在同一层内计算特异性
   └─ ID 选择器 > 类选择器 > 标签选择器

3. 源代码顺序
   └─ 后定义的样式覆盖先定义的

:has() 关系选择器原理

:has() 被称为 CSS 的"父选择器",可以根据子元素或后续兄弟元素的状态来选择父元素。

匹配机制

code
:has() 匹配流程:

┌─────────────────────────────────────┐
│           DOM 树                    │
│  ┌─────────┐                        │
│  │  form   │                        │
│  │ ┌─────┐ │                        │
│  │ │input│ │ ← 检查 input 状态     │
│  │ │:invalid│                       │
│  │ └─────┘ │                        │
│  └─────────┘                        │
│       ↑                              │
│  form:has(:invalid) 匹配成功        │
└─────────────────────────────────────┘

性能考虑

:has() 的性能影响:

  • 简单选择器:性能影响较小
  • 深层嵌套:可能导致性能问题
  • 动态内容:需要实时检查 DOM 变化
css
/* ✅ 推荐:简单选择器 */
.card:has(img) { }

/* ⚠️ 谨慎:深层嵌套 */
.card:has(.content .wrapper .image) { }

/* ✅ 推荐:限制范围 */
.card:has(> img) { }  /* 直接子元素 */

容器查询原理

容器查询允许根据容器尺寸而非视口尺寸应用样式,是组件级响应式设计的核心方案。

工作原理

code
容器查询工作流程:

┌─────────────────────────────────────┐
│  容器元素 (container-type: inline-size) │
│  ┌───────────────────────────────┐  │
│  │  子元素内容                    │  │
│  │                               │  │
│  │  @container (min-width: 400px)│  │
│  │  └─ 检查容器宽度               │  │
│  │  └─ 应用对应样式               │  │
│  └───────────────────────────────┘  │
└─────────────────────────────────────┘

容器类型对比

类型说明使用场景性能
inline-size查询行向尺寸(宽度)✅ 最常用较好
size查询行向和块向尺寸需要高度查询时较差
normal默认值,不作为查询容器

@property 类型系统原理

@property 允许为 CSS 自定义属性注册类型、初始值和继承性,使变量具备类型约束和动画能力。

类型注册流程

code
@property 注册流程:

1. 声明属性名称
   └─ --property-name

2. 定义语法类型
   └─ syntax: '<color>' | '<length>' | ...

3. 设置继承性
   └─ inherits: true | false

4. 指定初始值
   └─ initial-value: <value>

动画能力对比

css
/* 未注册的变量:无法动画 */
:root {
  --color: #007bff;
}

.element {
  background: var(--color);
  transition: --color 0.3s;  /* ❌ 无效 */
}

/* 注册的变量:可以动画 */
@property --color {
  syntax: '<color>';
  inherits: false;
  initial-value: #007bff;
}

.element {
  background: var(--color);
  transition: --color 0.3s;  /* ✅ 有效 */
}

View Transitions API 原理

View Transitions API 提供了一种简单的方式在不同 DOM 状态之间创建平滑的过渡动画。

过渡流程

code
View Transition 工作流程:

┌─────────────────────────────────────┐
│  1. 调用 startViewTransition()      │
├─────────────────────────────────────┤
│  2. 浏览器捕获旧状态截图            │
│     └─ ::view-transition-old        │
├─────────────────────────────────────┤
│  3. 执行 DOM 更新回调               │
├─────────────────────────────────────┤
│  4. 浏览器捕获新状态截图            │
│     └─ ::view-transition-new        │
├─────────────────────────────────────┤
│  5. 执行过渡动画                    │
│     └─ old → new 平滑过渡           │
└─────────────────────────────────────┘

代码示例

CSS 嵌套(Nesting)

基础语法

css
.card {
  padding: 1rem;
  background: #fff;
  border-radius: 8px;

  /* 后代选择器 */
  & .title {
    font-size: 1.5rem;
    font-weight: bold;
  }

  & .body {
    margin-top: 0.5rem;
    line-height: 1.6;
  }

  /* 伪类 */
  &:hover {
    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  }

  /* BEM 修饰符 */
  &--featured {
    border-left: 4px solid #007bff;
  }
}

嵌套 @规则

css
.component {
  font-size: 1rem;

  /* 嵌套媒体查询 */
  @media (min-width: 768px) {
    font-size: 1.125rem;
  }

  /* 嵌套容器查询 */
  @container (min-width: 400px) {
    display: flex;
  }

  /* 嵌套特性检测 */
  @supports (backdrop-filter: blur(10px)) {
    backdrop-filter: blur(10px);
  }
}

隐式嵌套

css
/* 省略 & 的写法 */
.nav {
  .item {
    padding: 0.5rem;

    .link {
      color: #007bff;
    }
  }
}

/* 等价于 */
.nav .item {
  padding: 0.5rem;
}

.nav .item .link {
  color: #007bff;
}

完整示例:卡片组件

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>CSS 嵌套示例</title>
  <style>
    .card {
      max-width: 400px;
      margin: 20px auto;
      padding: 1.5rem;
      background: white;
      border-radius: 12px;
      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
      transition: transform 0.3s, box-shadow 0.3s;

      & .card-header {
        display: flex;
        align-items: center;
        gap: 1rem;
        margin-bottom: 1rem;

        & .avatar {
          width: 48px;
          height: 48px;
          border-radius: 50%;
          background: linear-gradient(135deg, #667eea, #764ba2);
        }

        & .info {
          & .name {
            font-weight: 600;
            font-size: 1.1rem;
          }

          & .date {
            font-size: 0.875rem;
            color: #666;
          }
        }
      }

      & .card-body {
        line-height: 1.6;
        color: #333;
      }

      & .card-footer {
        margin-top: 1rem;
        padding-top: 1rem;
        border-top: 1px solid #eee;
        display: flex;
        gap: 1rem;

        & .action-btn {
          padding: 0.5rem 1rem;
          border: none;
          border-radius: 6px;
          cursor: pointer;
          transition: background 0.2s;

          &.primary {
            background: #007bff;
            color: white;

            &:hover {
              background: #0056b3;
            }
          }

          &.secondary {
            background: #f0f0f0;
            color: #333;

            &:hover {
              background: #e0e0e0;
            }
          }
        }
      }

      /* 悬停效果 */
      &:hover {
        transform: translateY(-4px);
        box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
      }

      /* 特色卡片 */
      &--featured {
        border: 2px solid #007bff;
        background: linear-gradient(135deg, #f8f9ff, #fff);
      }
    }
  </style>
</head>
<body style="background: #f5f5f5; padding: 40px;">
  <div class="card">
    <div class="card-header">
      <div class="avatar"></div>
      <div class="info">
        <div class="name">张三</div>
        <div class="date">2024-01-15</div>
      </div>
    </div>
    <div class="card-body">
      <p>这是一个使用 CSS 嵌套编写的卡片组件示例。嵌套语法让样式组织更加清晰和直观。</p>
    </div>
    <div class="card-footer">
      <button class="action-btn primary">点赞</button>
      <button class="action-btn secondary">评论</button>
    </div>
  </div>
</body>
</html>

CSS 层叠层(@layer)

基本用法

css
/* 声明层的顺序 */
@layer base, components, utilities;

/* 定义各层内容 */
@layer base {
  h1 { font-size: 2rem; margin: 0; }
  p { line-height: 1.6; }
}

@layer components {
  .card { padding: 1rem; border-radius: 8px; }
  .button { padding: 0.5rem 1rem; }
}

@layer utilities {
  .hidden { display: none !important; }
  .text-center { text-align: center; }
}

实际应用场景

css
/* 完整的项目层叠结构 */
@layer reset, base, components, utilities;

/* 重置层 */
@layer reset {
  *, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
  }
}

/* 基础层 */
@layer base {
  body {
    font-family: system-ui, sans-serif;
    line-height: 1.5;
    color: #333;
  }

  a {
    color: #007bff;
    text-decoration: none;
  }
}

/* 组件层 */
@layer components {
  .btn {
    padding: 0.5rem 1rem;
    border-radius: 4px;
    cursor: pointer;
    border: none;
    font-weight: 500;
  }

  .btn-primary {
    background: #007bff;
    color: white;
  }

  .btn-secondary {
    background: #6c757d;
    color: white;
  }
}

/* 工具层 */
@layer utilities {
  .mt-4 { margin-top: 1rem; }
  .mb-4 { margin-bottom: 1rem; }
  .text-center { text-align: center; }
  .hidden { display: none; }
}

/* 第三方库样式放入低优先级层 */
@layer third-party {
  @import url('bootstrap.css');
}

@layer 与 @import

css
/* 将外部样式表导入到指定层 */
@import url('reset.css') layer(reset);
@import url('base.css') layer(base);
@import url('components.css') layer(components);

@layer 与优先级

css
@layer low {
  .text { color: red; }     /* 低优先级 */
}

@layer high {
  .text { color: blue; }    /* 高优先级 */
}

/* 不在任何层中,优先级最高 */
.text { color: green; }

:has() 关系选择器

基础用法

css
/* 选择包含 img 的 a 标签 */
a:has(img) {
  display: block;
  border: 1px solid #eee;
}

/* 选择包含错误输入的表单 */
form:has(:invalid) {
  border: 2px solid #dc3545;
}

/* 选择子元素被选中的标签 */
label:has(input:checked) {
  font-weight: bold;
  color: #007bff;
}

基于兄弟元素选择

css
/* 选择后面紧跟 p 的 h2 */
h2:has(+ p) {
  margin-bottom: 0.5em;
}

/* 选择后面有 figcaption 的 figure */
figure:has(figcaption) {
  padding-bottom: 2em;
}

/* 选择前面有 h1 的 p */
p:has(~ h1) {
  font-size: 1.2rem;
}

复杂选择

css
/* 选择包含特色图片的卡片 */
.card:has(img[src*="featured"]) {
  border: 2px solid gold;
}

/* 选择空卡片 */
.card:not(:has(*)) {
  display: none;
}

/* 选择包含多个条件的元素 */
section:has(h2):has(p) {
  padding: 2rem;
}

/* 结合 :not() 使用 */
.card:not(:has(.badge)) {
  border: 1px solid #ddd;
}

实际应用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>:has() 选择器示例</title>
  <style>
    /* 表单验证反馈 */
    .form-group {
      margin-bottom: 1rem;
    }

    .form-group:has(input:invalid) {
      background: #fff5f5;
      padding: 1rem;
      border-radius: 8px;
    }

    .form-group:has(input:invalid) .error-message {
      display: block;
      color: #dc3545;
      font-size: 0.875rem;
      margin-top: 0.5rem;
    }

    .form-group:has(input:valid) .success-icon {
      display: inline;
      color: #28a745;
    }

    .error-message {
      display: none;
    }

    .success-icon {
      display: none;
    }

    /* 卡片布局自适应 */
    .grid {
      display: grid;
      gap: 1rem;
      grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    }

    /* 当有3个卡片时,使用3列布局 */
    .grid:has(.card:nth-child(3)) {
      grid-template-columns: repeat(3, 1fr);
    }

    /* 卡片样式 */
    .card {
      padding: 1rem;
      border: 1px solid #ddd;
      border-radius: 8px;
    }

    /* 包含图片的卡片特殊样式 */
    .card:has(img) {
      display: flex;
      flex-direction: column;
    }

    .card:has(img) img {
      width: 100%;
      border-radius: 4px;
      margin-bottom: 0.5rem;
    }

    /* 暗色主题下包含图片的卡片 */
    .dark-theme .card:has(img) {
      background: #2d2d2d;
      border-color: #444;
    }
  </style>
</head>
<body>
  <h2>表单验证示例</h2>
  <form>
    <div class="form-group">
      <label>邮箱:</label>
      <input type="email" required>
      <span class="error-message">请输入有效的邮箱地址</span>
      <span class="success-icon">✓</span>
    </div>
    <div class="form-group">
      <label>用户名:</label>
      <input type="text" required minlength="3">
      <span class="error-message">用户名至少3个字符</span>
      <span class="success-icon">✓</span>
    </div>
  </form>

  <h2>卡片布局示例</h2>
  <div class="grid">
    <div class="card">
      <p>纯文本卡片</p>
    </div>
    <div class="card">
      <img src="https://picsum.photos/200/150?random=1" alt="图片">
      <p>带图片的卡片</p>
    </div>
    <div class="card">
      <img src="https://picsum.photos/200/150?random=2" alt="图片">
      <p>另一张带图片的卡片</p>
    </div>
  </div>
</body>
</html>

容器查询

基础用法

css
/* 定义容器 */
.card-container {
  container-type: inline-size;
  container-name: card;
}

/* 简写 */
.card-container {
  container: card / inline-size;
}

/* 容器查询 */
@container card (min-width: 400px) {
  .card {
    display: flex;
    gap: 1rem;
  }
}

容器查询单位

css
.card-container {
  container-type: inline-size;
}

/* 使用容器查询单位 */
.card-title {
  /* 字体大小基于容器宽度 */
  font-size: clamp(1rem, 3cqi, 2rem);
  padding: 1cqi 2cqi;
}

.card-image {
  /* 图片尺寸基于容器宽度 */
  width: 100cqi;
  max-width: 400px;
}

响应式卡片完整示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>容器查询示例</title>
  <style>
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }

    body {
      font-family: system-ui, sans-serif;
      padding: 20px;
      background: #f5f5f5;
    }

    .container {
      display: grid;
      grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
      gap: 20px;
      max-width: 1200px;
      margin: 0 auto;
    }

    /* 定义容器 */
    .card-container {
      container-type: inline-size;
      background: white;
      border-radius: 12px;
      overflow: hidden;
    }

    .card {
      display: grid;
      gap: 1rem;
      padding: 1rem;
    }

    .card-image {
      width: 100%;
      aspect-ratio: 16 / 9;
      object-fit: cover;
      border-radius: 8px;
    }

    .card-title {
      font-size: 1.25rem;
      font-weight: 600;
    }

    .card-description {
      color: #666;
      line-height: 1.6;
    }

    /* 容器宽度 >= 400px */
    @container (min-width: 400px) {
      .card {
        grid-template-columns: 150px 1fr;
      }

      .card-image {
        aspect-ratio: 1;
      }
    }

    /* 容器宽度 >= 600px */
    @container (min-width: 600px) {
      .card {
        grid-template-columns: 200px 1fr;
        padding: 1.5rem;
      }

      .card-title {
        font-size: 1.5rem;
      }
    }

    /* 调整容器大小演示 */
    .resize-handle {
      resize: horizontal;
      overflow: auto;
      border: 2px dashed #ccc;
      min-width: 250px;
      max-width: 100%;
    }
  </style>
</head>
<body>
  <h1>容器查询示例</h1>
  <p>拖动右下角调整容器大小,观察卡片布局变化</p>

  <div class="container">
    <div class="resize-handle">
      <div class="card-container">
        <div class="card">
          <img src="https://picsum.photos/400/300?random=1" class="card-image" alt="图片">
          <div>
            <h3 class="card-title">卡片标题</h3>
            <p class="card-description">这是一个响应式卡片示例。当容器宽度变化时,卡片布局会自动调整。</p>
          </div>
        </div>
      </div>
    </div>

    <div class="resize-handle">
      <div class="card-container">
        <div class="card">
          <img src="https://picsum.photos/400/300?random=2" class="card-image" alt="图片">
          <div>
            <h3 class="card-title">另一张卡片</h3>
            <p class="card-description">每张卡片都是独立的容器,可以根据自己的宽度调整布局。</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</body>
</html>

@property 自定义属性注册

基础语法

css
@property --property-name {
  syntax: '<type>';
  inherits: true | false;
  initial-value: <value>;
}

支持的类型

类型说明示例
<length>长度值0px, 16px
<number>数值0, 1.5
<percentage>百分比0%, 50%
<length-percentage>长度或百分比0px, 50%
<color>颜色值#000, red
<angle>角度值0deg, 45deg
<time>时间值0s, 300ms
<resolution>分辨率96dpi
<url>URL 值url(...)
<integer>整数0, 1
*任意值任意

使用示例

css
/* 注册颜色变量 */
@property --primary-color {
  syntax: '<color>';
  inherits: true;
  initial-value: #007bff;
}

/* 注册长度变量 */
@property --spacing {
  syntax: '<length>';
  inherits: false;
  initial-value: 16px;
}

/* 注册角度变量 */
@property --gradient-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

/* 使用注册的变量 */
.element {
  background: var(--primary-color);
  padding: var(--spacing);
}

@property 实现动画

css
/* 注册可动画的颜色变量 */
@property --hue {
  syntax: '<number>';
  inherits: false;
  initial-value: 0;
}

/* 渐变动画 */
.animated-gradient {
  background: linear-gradient(
    calc(var(--hue) * 1deg),
    #007bff, #7c3aed, #db2777
  );
  animation: hue-rotate 3s linear infinite;
}

@keyframes hue-rotate {
  to {
    --hue: 360;
  }
}

/* 边框宽度动画 */
@property --border-width {
  syntax: '<length>';
  inherits: false;
  initial-value: 0px;
}

.animated-border {
  border: var(--border-width) solid #007bff;
  transition: --border-width 0.3s ease;
}

.animated-border:hover {
  --border-width: 4px;
}

JavaScript 注册

javascript
// 使用 JavaScript 注册自定义属性
CSS.registerProperty({
  name: '--primary-color',
  syntax: '<color>',
  inherits: true,
  initialValue: '#007bff',
});

// 注册数值类型
CSS.registerProperty({
  name: '--progress',
  syntax: '<number>',
  inherits: false,
  initialValue: '0',
});

View Transitions API

基础用法

javascript
// 启动视图过渡
document.startViewTransition(() => {
  // 更新 DOM
  updateTheDOMSomeHow();
});

页面导航过渡

css
/* 旧视图淡出 */
::view-transition-old(root) {
  animation: fade-out 0.3s ease;
}

/* 新视图淡入 */
::view-transition-new(root) {
  animation: fade-in 0.3s ease;
}

@keyframes fade-out {
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
}

自定义过渡效果

css
/* 元素级过渡 */
.card {
  view-transition-name: card;
}

::view-transition-old(card) {
  animation: slide-out 0.3s ease;
}

::view-transition-new(card) {
  animation: slide-in 0.3s ease;
}

@keyframes slide-out {
  to { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in {
  from { transform: translateX(100%); opacity: 0; }
}

主题切换过渡

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>View Transitions 主题切换</title>
  <style>
    :root {
      --bg: #ffffff;
      --text: #333333;
    }

    .dark {
      --bg: #1a1a1a;
      --text: #ffffff;
    }

    body {
      background: var(--bg);
      color: var(--text);
      transition: background 0.3s, color 0.3s;
      min-height: 100vh;
      display: flex;
      flex-direction: column;
      align-items: center;
      justify-content: center;
      font-family: system-ui, sans-serif;
    }

    .toggle-btn {
      padding: 1rem 2rem;
      font-size: 1.125rem;
      border: none;
      border-radius: 8px;
      cursor: pointer;
      background: var(--text);
      color: var(--bg);
      transition: transform 0.2s;
    }

    .toggle-btn:hover {
      transform: scale(1.05);
    }

    /* View Transition 样式 */
    ::view-transition-old(root),
    ::view-transition-new(root) {
      animation: none;
      mix-blend-mode: normal;
    }

    ::view-transition-old(root) {
      z-index: 1;
    }

    ::view-transition-new(root) {
      z-index: 9999;
    }
  </style>
</head>
<body>
  <h1>View Transitions API</h1>
  <p>点击按钮切换主题,观察平滑过渡效果</p>
  <button class="toggle-btn" onclick="toggleTheme(event)">
    切换主题
  </button>

  <script>
    function toggleTheme(event) {
      // 检查浏览器支持
      if (!document.startViewTransition) {
        document.documentElement.classList.toggle('dark');
        return;
      }

      // 启动视图过渡
      document.startViewTransition(() => {
        document.documentElement.classList.toggle('dark');
      });
    }
  </script>
</body>
</html>

SPA 路由过渡

javascript
function navigateTo(url) {
  // 检查浏览器支持
  if (!document.startViewTransition) {
    location.href = url;
    return;
  }

  // 启动视图过渡
  document.startViewTransition(async () => {
    const response = await fetch(url);
    const html = await response.text();
    const parser = new DOMParser();
    const doc = parser.parseFromString(html, 'text/html');
    document.querySelector('main').innerHTML = doc.querySelector('main').innerHTML;
  });
}

@scope 作用域样式

基础语法

css
/* 将样式限制在 .card 内部 */
@scope (.card) {
  .title {
    font-size: 1.5rem;
  }

  .body {
    line-height: 1.6;
  }
}

带下限的作用域

css
/* 样式作用于 .card 内部,但不作用于 .card-content 内部 */
@scope (.card) to (.card-content) {
  .title {
    color: blue;
  }
}

与 Shadow DOM 的区别

特性@scopeShadow DOM
样式隔离限定范围完全隔离
JavaScript无影响隔离 DOM
选择器穿透支持需 CSS 变量
复杂度

@starting-style

基础用法

css
.dialog {
  opacity: 0;
  transform: translateY(20px);
  transition: opacity 0.3s, transform 0.3s;

  /* 定义首次渲染时的初始样式 */
  @starting-style {
    opacity: 0;
    transform: translateY(20px);
  }
}

.dialog[open] {
  opacity: 1;
  transform: translateY(0);
}

应用场景

css
/* 弹窗入场动画 */
.modal {
  transition: opacity 0.3s, transform 0.3s;
  opacity: 0;
  transform: scale(0.9);

  @starting-style {
    opacity: 0;
    transform: scale(0.9);
  }
}

.modal.active {
  opacity: 1;
  transform: scale(1);
}

/* 元素首次出现动画 */
.notification {
  animation: slide-in 0.3s ease;

  @starting-style {
    transform: translateX(100%);
  }
}

@keyframes slide-in {
  to {
    transform: translateX(0);
  }
}

滚动驱动动画

基础语法

css
.scroll-progress {
  transform: scaleX(0);
  transform-origin: left;
  animation: progress linear;
  animation-timeline: scroll();
}

@keyframes progress {
  to { transform: scaleX(1); }
}

滚动进度条

css
.progress-bar {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 3px;
  background: #007bff;
  transform-origin: left;
  transform: scaleX(0);
  animation: scale-x linear;
  animation-timeline: scroll();
}

@keyframes scale-x {
  to { transform: scaleX(1); }
}

视差滚动

css
.parallax-bg {
  animation: parallax linear;
  animation-timeline: scroll();
}

@keyframes parallax {
  from { transform: translateY(0); }
  to { transform: translateY(-200px); }
}

元素滚动驱动

css
.reveal-element {
  animation: reveal linear both;
  animation-timeline: view();
  animation-range: entry 0% entry 100%;
}

@keyframes reveal {
  from {
    opacity: 0;
    transform: translateY(50px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

最佳实践

渐进增强策略

css
/* 使用 @supports 检测特性支持 */
@supports (selector(:has(*))) {
  .card:has(img) {
    display: flex;
  }
}

@supports (container-type: inline-size) {
  .card-container {
    container-type: inline-size;
  }
}

/* 使用 CSS 嵌套作为增强 */
.card {
  padding: 1rem;
}

@supports (selector(&)) {
  .card {
    padding: 1rem;

    &:hover {
      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
    }
  }
}

特性组合使用

css
@layer components {
  .card {
    container-type: inline-size;

    & .title {
      font-size: clamp(1rem, 3cqi, 1.5rem);
    }

    @container (min-width: 400px) {
      display: flex;
    }
  }
}

性能考虑

  • :has() 在复杂选择器中可能影响性能,避免深层嵌套
  • 容器查询的 container-type: sizeinline-size 开销更大
  • @layer 不影响运行时性能,仅影响层叠顺序
  • View Transitions 会创建截图,大元素可能消耗内存
  • 滚动驱动动画由浏览器优化,性能优于 JavaScript 滚动监听

浏览器兼容性处理

css
/* 提供回退方案 */
.card {
  /* 基础样式 */
  padding: 1rem;
}

/* 嵌套增强 */
@supports (selector(&)) {
  .card {
    & .title {
      font-size: 1.5rem;
    }
  }
}

/* 容器查询增强 */
@supports (container-type: inline-size) {
  .card-container {
    container-type: inline-size;
  }

  @container (min-width: 400px) {
    .card {
      display: flex;
    }
  }
}

常见问题

Q1: CSS 嵌套和 Sass 嵌套有什么区别?

A: 主要区别:

特性CSS 嵌套Sass 嵌套
执行环境浏览器原生预处理器编译
性能无额外开销编译时处理
语法需要 &可选 &
功能基础嵌套完整功能(循环、条件等)
css
/* CSS 嵌套 */
.card {
  & .title { }
}

/* Sass 嵌套 */
.card {
  .title { }  /* 可以省略 & */
}

Q2: @layer 会影响 !important 的优先级吗?

A: !important 的优先级规则不变,但层叠顺序会影响:

css
@layer base {
  .text { color: red !important; }  /* 低优先级的 !important */
}

@layer utilities {
  .text { color: blue !important; } /* 高优先级的 !important */
}

.text { color: green !important; }  /* 最高优先级 */

Q3: :has() 的性能影响大吗?

A: 取决于选择器复杂度:

css
/* ✅ 性能好:简单选择器 */
.card:has(img) { }

/* ⚠️ 谨慎使用:深层嵌套 */
.card:has(.content .wrapper .image) { }

/* ✅ 推荐:限制范围 */
.card:has(> img) { }  /* 直接子元素 */

Q4: 容器查询和媒体查询可以同时使用吗?

A: 可以,它们解决不同的问题:

css
/* 媒体查询:基于视口 */
@media (min-width: 768px) {
  .container {
    flex-direction: row;
  }
}

/* 容器查询:基于组件容器 */
@container (min-width: 400px) {
  .card {
    display: flex;
  }
}

Q5: @property 注册的变量可以继承吗?

A: 取决于 inherits 设置:

css
@property --color {
  syntax: '<color>';
  inherits: true;  /* 可以继承 */
  initial-value: #007bff;
}

@property --size {
  syntax: '<length>';
  inherits: false;  /* 不能继承 */
  initial-value: 16px;
}

.parent {
  --color: red;
  --size: 20px;
}

.child {
  color: var(--color);  /* 继承 red */
  font-size: var(--size);  /* 使用初始值 16px */
}

Q6: View Transitions API 在 Firefox 中如何使用?

A: Firefox 目前不支持,可以使用 JavaScript 作为回退:

javascript
function transition(callback) {
  if (document.startViewTransition) {
    document.startViewTransition(callback);
  } else {
    // 回退方案:简单的淡入淡出
    document.body.style.opacity = '0';
    setTimeout(() => {
      callback();
      document.body.style.opacity = '1';
    }, 300);
  }
}

Q7: 滚动驱动动画如何添加 JavaScript 回退?

A: 使用 Intersection Observer 作为回退:

javascript
// 检查浏览器支持
if (!CSS.supports('animation-timeline', 'view()')) {
  // 使用 Intersection Observer
  const observer = new IntersectionObserver((entries) => {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        entry.target.classList.add('visible');
      }
    });
  }, { threshold: 0.1 });

  document.querySelectorAll('.reveal').forEach(el => {
    observer.observe(el);
  });
}

参考资源