View Transitions API 自 2023 年问世以来,为 Web 应用带来了原生级的状态切换动画能力。通过 startViewTransition() 和 ::view-transition-* 伪元素,开发者可以轻松实现 DOM 状态之间的平滑过渡。然而,随着应用场景的复杂化,三个关键问题逐渐浮现:

  1. 如何根据不同的用户操作(向前翻页/向后翻页、打开/关闭模态框)触发不同的过渡动画?

  2. 如何将多个独立的过渡元素“分组”管理,统一控制它们的动画样式?

  3. 视图过渡的伪元素层级是怎样的?如何正确使用 ::view-transition-group、::view-transition-old、::view-transition-new 来施加动画?

Level 2 规范同时引入了多套互补机制:
  • 过渡类型(View Transition Types):包括 transition.types 属性和 :active-view-transition-type() 伪类,用于根据操作类型选择动画。

  • 过渡类(view-transition-class):允许为具有不同 view-transition-name 的元素分配共享类标识,实现动画样式的分组复用。

  • 伪元素体系:五大 ::view-* 伪元素构成了舞台、分组、图像对、旧状态、新状态的完整叙事语法。

本文将以“声明与执行分离”的架构视角,系统对比传统类名模式与现代过渡类型方式的优劣,深入解析 view-transition-class 的使用方法,并完整阐述五大伪元素的层级关系与协作模型,帮助你全面掌握 CSS 视图过渡的高级特性。

一、问题背景:为什么需要区分动画、分组与伪元素?

在一个典型应用中,不同的用户操作往往需要不同的过渡效果:

用户操作

期望的过渡效果

点击“下一页”

旧内容向左滑出,新内容从右侧滑入

点击“上一页”

旧内容向右滑出,新内容从左侧滑入

打开模态框

内容缩放消失,模态框放大出现

关闭模态框

模态框缩小消失,内容缩放恢复

同时,页面中可能存在多个需要独立动画的元素(如多个卡片、多个图片),它们的动画参数(时长、缓动函数)往往需要保持一致。而每个参与过渡的元素必须有唯一的 view-transition-name,这就导致了样式重复的问题。

最后,所有这些动画最终需要通过伪元素来施加——

  • ::view-transition-group 控制容器变换,

  • ::view-transition-old 控制旧状态退出,

  • ::view-transition-new 控制新状态进入。

理解伪元素的层级与分工,是正确编写视图过渡样式的前提。

二、传统方案:临时类名方式(回顾与痛点)

在 :active-view-transition-type() 出现之前,开发者通过在根元素上添加临时类名来“标记”当前过渡的类型。

.page-next::view-transition-old(root) {
  animation: slideOutLeft 0.3s ease-in forwards;
}
.page-next::view-transition-new(root) {
  animation: slideInRight 0.3s ease-out forwards;
}

async function navigate(direction) {
  const transition = document.startViewTransition(() => updatePage(direction));
  const className = direction === 'next' ? 'page-next' : 'page-prev';
  document.documentElement.classList.add(className);
  await transition.finished;
  document.documentElement.classList.remove(className);
}

缺点:手动生命周期管理、多场景组合爆炸、全局 DOM 污染、调试残留。这些痛点催生了 Level 2 的过渡类型机制。

三、现代方案一:过渡类型方式(:active-view-transition-type)

3.1 核心概念

开发者可以为一次过渡附加一个或多个字符串类型,这些类型作为过渡对象的元数据存在,不污染 DOM。CSS 通过 :active-view-transition-type() 伪类来匹配这些类型。

3.2 三种使用方式

方式一:通过 startViewTransition 的 types 选项(SPA)

document.startViewTransition({
  update: () => updatePageContent(),
  types: ['page-next']
});

方式二:通过 transition.types.add() 方法(动态添加)

const transition = document.startViewTransition(() => updateDOM());
transition.types.add('page-next');
transition.types.add('smooth');

方式三:通过 @view-transition 的 types 描述符(MPA 跨文档过渡)

@view-transition {
  navigation: auto;
  types: page-next, page-prev;
}
3.3 CSS 匹配与示例
:active-view-transition-type(page-next)::view-transition-old(root) {
  animation: slideOutLeft 0.3s ease-in forwards;
}
:active-view-transition-type(page-next)::view-transition-new(root) {
  animation: slideInRight 0.3s ease-out forwards;
}
:active-view-transition-type(page-prev)::view-transition-old(root) {
  animation: slideOutRight 0.3s ease-in forwards;
}
:active-view-transition-type(page-prev)::view-transition-new(root) {
  animation: slideInLeft 0.3s ease-out forwards;
}
3.4 优点总结
  • 无手动清理,类型随过渡对象自动销毁。

  • 支持多类型叠加,CSS 可用 :active-view-transition-type(a, b) 精确匹配。

  • 不污染 DOM,仅存在于内存。

  • 语义清晰。

四、现代方案二:view-transition-class 分组管理

4.1 解决什么问题?

多个具有不同 view-transition-name 的元素,如果动画参数需要保持一致,传统方式需要重复编写样式。view-transition-class 允许为它们分配共享类标识,统一控制动画。

4.2 基本语法
  • 属性:view-transition-class。

  • 可用值:none | <custom-ident>+。

  • 默认值:none。

  • 适用范围:所有元素。

  • 继承性:不继承。

/* 为元素分配唯一的过渡名称和共享的过渡类 */
.element {
  view-transition-name: unique-bear;
  view-transition-class: bear-group;
}

/* 通过类名统一设置组内所有元素的动画 */
::view-transition-group(.bear-group) {
  animation-duration: 500ms;
}
4.3 使用对比

传统方式(代码重复):

.element-1 { view-transition-name: item-1; animation-duration: 2s; }
.element-2 { view-transition-name: item-2; animation-duration: 2s; }

使用 view-transition-class

.element-1, .element-2 {
  view-transition-class: shared-animation;
}
::view-transition-group(.shared-animation) {
  animation-duration: 2s;
}
4.4 多类名与注意事项

单个元素可以分配多个过渡类:

.element {
  view-transition-class: base-animation fast-timing special-effect;
}
::view-transition-group(.base-animation) { animation-duration: 0.5s; }
::view-transition-group(.fast-timing) { animation-duration: 0.2s; }

选择器书写规范:伪元素中必须加点号前缀,如 ::view-transition-group(.my-class),不能写成 (my-class)。不能在同一选择器中混合名称和类,如 ::view-transition-group(.myClass itemName) 无效。

五、视图伪元素完全解析

视图过渡在底层为每个命名的过渡元素创建以下伪元素层级树:

::view-transition
└── ::view-transition-group(root) 或 ::view-transition-group(名称)
    └── ::view-transition-image-pair(名称)
        ├── ::view-transition-old(名称)   /* 旧状态快照 */
        └── ::view-transition-new(名称)   /* 新状态快照 */
5.1 ::view-transition —— 舞台本身
  • 角色:覆盖整个视口的叠加层,z-index 极高,确保动画不被干扰。

  • 默认样式:position: fixed; inset: 0; pointer-events: none;

  • 典型应用:添加全局背景色、毛玻璃效果或调试边框。

::view-transition {
  background: radial-gradient(circle, transparent 30%, rgba(0,0,0,0.2) 100%);
}
5.2 ::view-transition-group —— 分组容器
  • 角色:每个命名元素的独立容器,控制整个过渡组的变换、位置、尺寸。是动画应用的最主要目标。

  • 选择器语法:通过名称 ::view-transition-group(header) 或通过类 ::view-transition-group(.card-group)(Level 2)。

  • 典型应用:统一动画时长、位置移动、尺寸过渡。

::view-transition-group(.product-card) {
  animation-duration: 0.6s;
  animation-timing-function: cubic-bezier(0.34, 1.56, 0.64, 1);
}
::view-transition-group(cart-icon) {
  animation: moveToCart 0.4s ease-out;
}
5.3 ::view-transition-image-pair —— 叠画转场台
  • 角色:旧状态快照和新状态快照的父容器,控制混合与叠加。

  • 默认样式:position: absolute; inset: 0; isolation: isolate; pointer-events: none;

  • 典型应用:自定义混合模式(mix-blend-mode)。

::view-transition-image-pair(modal-container) {
  mix-blend-mode: multiply;
}
  • 注意:大多数情况下不需要直接操作,修改 isolation 或 mix-blend-mode 需谨慎。

5.4 ::view-transition-old —— 记忆中的旧画面
  • 角色:过渡开始前的元素状态快照(替换元素,不可访问内部 DOM)。

  • 默认样式:position: absolute; inset: 0; object-fit: contain;

  • 典型应用:淡出、滑出、缩放消失动画。

::view-transition-old(.card-transition) {
  animation: fadeOut 0.4s ease-in forwards;
}
@keyframes fadeOut { to { opacity: 0; } }
5.5 ::view-transition-new —— 崭新登场的主角
  • 角色:过渡完成后的元素状态快照。

  • 默认样式:同 old,但初始 opacity: 0(需要动画使其显现)。

  • 典型应用:淡入、滑入、弹性弹出动画。

::view-transition-new(.card-transition) {
  animation: fadeIn 0.4s ease-out 0.1s both;
}
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }

六、视图属性与伪元素的协作体系

6.1 核心协作模型
【声明阶段】                   【执行阶段】                      【呈现阶段】
view-transition-name  →  映射为  →  ::view-transition-group(名称)   →  动画施加
view-transition-class →  聚合为  →  ::view-transition-group(.类名)  →  样式复用
6.2 五阶段协作流程
  1. 注册演员:view-transition-name: card-1;

  2. 分配戏服:view-transition-class: card-group;(Level 2);

  3. 搭建舞台:浏览器自动创建 ::view-transition;

  4. 设置机位:通过伪元素选择器指定动画目标;

  5. 指导表演:定义 ::view-transition-old 和 ::view-transition-new 动画。

6.3 协作关系速查表

协作伪元素

协作关系

作用阶段

视图属性:view-transition-name: foo

::view-transition-group(foo)

1:1 映射

标识绑定

::view-transition-image-pair(foo)

1:1 映射

容器嵌套

::view-transition-old(foo)

1:1 映射

状态快照

::view-transition-new(foo)

1:1 映射

状态快照

视图属性:view-transition-class: bar

::view-transition-group(.bar)

N:1 聚合

样式复用

::view-transition-old(.bar)

N:1 聚合

样式复用

::view-transition-new(.bar)

N:1 聚合

样式复用

6.4 组合使用示例
/* 仅当过渡类型为 'highlight' 时,对 .special-card 组应用弹性动画 */
:active-view-transition-type(highlight)::view-transition-group(.special-card) {
  animation: elastic 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
}

/* 同时为多个过渡类设置默认动画 */
::view-transition-group(.card-transition) {
  animation-duration: 0.4s;
}

七、浏览器兼容性与渐进增强

特性

支持版本

基础视图过渡(Level 1)

Chrome 111+

transition.types 与 :active-view-transition-type()

Chrome 126+、Safari 18.2+

view-transition-class

Chrome 126+、Safari 18.2+(Level 2)

Firefox

开发中(预计未来支持)

渐进增强策略

async function startTransition(updateCallback, typeName) {
  if (!document.startViewTransition) {
    updateCallback();
    return;
  }
  const transition = document.startViewTransition(updateCallback);
  if (transition.types && typeof transition.types.add === 'function') {
    transition.types.add(typeName);
  } else {
    // 降级:类名方式
    document.documentElement.classList.add(`transition-${typeName}`);
    await transition.finished;
    document.documentElement.classList.remove(`transition-${typeName}`);
  }
}

同时可通过 CSS @supports 进行特性检测:

@supports (view-transition-class: test) {
  .element { view-transition-class: modern-group; }
}

尊重用户偏好:

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*), ::view-transition-old(*), ::view-transition-new(*) {
    animation: none !important;
  }
}

八、最佳实践总结

  1. 职责分离:view-transition-name/class 只负责标识与分组,伪元素只负责动画样式。

  2. 群组优先,个体覆盖:先通过 view-transition-class 定义通用规则,再为特殊元素单独覆盖。

  3. 优先使用过渡类型而非临时类名:避免手动生命周期管理和 DOM 污染。

  4. 合理使用伪元素:大多数动画只需操作 ::view-transition-group、::view-transition-old、::view-transition-new;::view-transition-image-pair 仅在需要混合模式时使用;::view-transition 用于全局装饰。

  5. 始终提供降级方案:检测 API 支持,回退到传统过渡或无动画。

  6. 类型名与类名语义化:使用如 page-next、card-transition,避免保留关键字(none、ua- 前缀等)。

总结

维度

临时类名方式

:active-view-transition-type()

view-transition-class

核心理念

动画选择 = 全局 DOM 状态

动画选择 = 过渡对象元数据

动画复用 = 元素分组标识

生命周期

手动添加/移除

自动随过渡销毁

随元素存在,样式自动应用

适用场景

简单项目、旧浏览器兼容

多动画复杂交互

多个元素统一动画参数

视图过渡的完整工作流

【声明】view-transition-name/class  →  【触发】startViewTransition + types  →  【执行】::view-* 伪元素动画

transition.types 与 :active-view-transition-type() 将动画选择从命令式变为声明式,view-transition-class 将样式复用从元素标识中解耦,而五大伪元素则提供了从舞台到单个快照的精细控制能力。三者共同构成了 CSS 视图过渡的完整叙事语法—— 命名演员、分配类型、分组管理、搭建舞台、指导表演

掌握这套协作体系,你就能以声明式的优雅姿态,构建出以往需要复杂 JavaScript 状态管理和时间轴控制的流畅过渡体验。随着浏览器支持的不断完善,这些特性将成为现代 Web 动画的标配工具。

彩蛋

本文配有完整示例,点击《视图过渡综合演示:类型 + 分组 + 伪元素》即可查看。