View Transitions API 自 2023 年问世以来,为 Web 应用带来了原生级的状态切换动画能力。通过 startViewTransition() 和 ::view-transition-* 伪元素,开发者可以轻松实现 DOM 状态之间的平滑过渡。然而,随着应用场景的复杂化,三个关键问题逐渐浮现:
如何根据不同的用户操作(向前翻页/向后翻页、打开/关闭模态框)触发不同的过渡动画?
如何将多个独立的过渡元素“分组”管理,统一控制它们的动画样式?
视图过渡的伪元素层级是怎样的?如何正确使用 ::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 五阶段协作流程
注册演员:view-transition-name: card-1;
分配戏服:view-transition-class: card-group;(Level 2);
搭建舞台:浏览器自动创建 ::view-transition;
设置机位:通过伪元素选择器指定动画目标;
指导表演:定义 ::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;
}
}八、最佳实践总结
职责分离:view-transition-name/class 只负责标识与分组,伪元素只负责动画样式。
群组优先,个体覆盖:先通过 view-transition-class 定义通用规则,再为特殊元素单独覆盖。
优先使用过渡类型而非临时类名:避免手动生命周期管理和 DOM 污染。
合理使用伪元素:大多数动画只需操作 ::view-transition-group、::view-transition-old、::view-transition-new;::view-transition-image-pair 仅在需要混合模式时使用;::view-transition 用于全局装饰。
始终提供降级方案:检测 API 支持,回退到传统过渡或无动画。
类型名与类名语义化:使用如 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 动画的标配工具。
彩蛋
本文配有完整示例,点击《视图过渡综合演示:类型 + 分组 + 伪元素》即可查看。
