还记得 2023 年,偶然在 antfu 大佬的一次直播里听到 View Transitions API 这个新鲜名词。
当时只在脑子里浅浅留了个印象,没深入。直到最近项目里有了具体需求,我才真正静下心来把 view-transitions-api 研究了一遍,并用到实际开发中。
什么是 View Transitions API
View Transitions API 是一种用于创建动画的浏览器 API,常用于 SPA(单页面应用)里的过渡切换动画。 用它可以在不同 DOM 状态之间轻松做转场,省掉一大堆处理新旧内容共存的 CSS 和 JavaScript。
工作原理
View Transitions API 的原理,是浏览器自己接管页面视图变化的管理和动画处理。
页面内容需要更新时,开发者调用 document.startViewTransition(callback) 启动视图过渡。浏览器在后台自动创建两个快照:更新前的页面和更新后的页面。
::view-transition
└─ ::view-transition-group(root)
└─ ::view-transition-image-pair(root)
├─ ::view-transition-old(root)
└─ ::view-transition-new(root)
- ::view-transition 是视图过渡叠加层的根元素,它包含所有视图过渡且位于所有其他页面内容的顶部。
- ::view-transition-old 是旧页面视图的屏幕截图,且 ::view-transition-new 是新页面视图的实时展示。
然后浏览器分析两个页面的差异,确定哪些元素要做过渡,并根据开发者设置的持续时间、缓动效果等,算出每个元素的动画路径。
过渡时,浏览器同时显示新旧两套元素,按算好的路径平滑变换。比如某个元素的位置、大小或颜色变了,会以动画过渡过去,而不是硬切。
再比如更新列表里的某一项时,浏览器能识别出这项的变化,把它从旧状态渐渐过渡到新状态,而不是直接换掉内容。
通过修改伪元素树实现自定义动画效果
动画开始前,会兑现 Promise ViewTransition.ready,这时可以用 JavaScript 创建自定义动画。
也可以通过覆盖原本的 CSS 来自定义动画。默认的 CSS 动画是:旧页面视图不透明度由 1 变到 0,新页面视图不透明度由 0 变到 1,时长 0.25 秒。
动画执行完后,会兑现 Promise ViewTransition.finished。
View Transitions API 的用法
原理讲完了,接下来看它怎么用。
Document:startViewTransition() 方法
View Transitions API 的 startViewTransition() 方法会开启一个新的视图过渡,并返回一个表示它的 ViewTransition 对象。
const transition = document.startViewTransition(callback);
callback: 通常用来在过渡过程中更新 DOM 的回调函数,返回一个 Promise。 它在 API 截取当前页面截图之后被调用。回调返回的 Promise 兑现时,视图过渡在下一帧开始;如果这个 Promise 被拒绝,过渡就会被放弃。
- ready: 伪元素树创建好、动画即将开始时兑现
transition.ready.then(() => {
// ......
});
- finished: 动画结束后兑现,此时新页面视图对用户可见且可交互
transition.finished.then(() => {
// ......
});
自定义过渡动画
除了默认动画,还可以借助 CSS 修改伪元素来自定义过渡,比如改动画的时间、延迟、名称等。
这里以本站浅色 / 暗色模式切换功能代码为例
::view-transition-new(root) {
animation: turnOff 800ms ease-in-out;
}
::view-transition-old(root) {
animation: none;
}
/* 新视图过渡 */
@keyframes turnOn {
0% {
clip-path: circle(0% at var(--click-x) var(--click-y));
}
100% {
clip-path: circle(var(--end-radius) at var(--click-x) var(--click-y));
}
}
.dark::view-transition-new(root) {
animation: turnOn 800ms ease-in-out;
}
::view-transition-old(root) {
animation: none;
}
/* 旧视图过渡 */
@keyframes turnOff {
0% {
clip-path: circle(0% at var(--click-x) var(--click-y));
}
100% {
clip-path: circle(var(--end-radius) at var(--click-x) var(--click-y));
}
}
上面这段代码中的 --click-x、--click-y、--end-radius 值会在点击主题切换时触发并计算,在开始时(0%),clip-path (裁剪路径)的值为一个半径为 0% 的圆形,位置由变量 --click-x 和 --click-y 决定;
在结束时(100%),clip-path 变为一个半径由变量 --end-radius 决定的圆形,位置同样由 --click-x 和 --click-y 决定。
动画效果为从点击位置扩散开的一个圆形遮罩,具体效果查看下图或点击页面右上角主题切换按钮尝试。

除了用 CSS,也可以用 JS 做更复杂的过渡动画。等 ViewTransition.ready 完成时,用 document.documentElement.animate 来驱动。
这里以 antfu 大佬的的 Blog 主题切换代码为例
export function toggleDark(event: MouseEvent) {
const isAppearanceTransition =
document.startViewTransition && !window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (!isAppearanceTransition) {
isDark.value = !isDark.value;
return;
}
const x = event.clientX;
const y = event.clientY;
const endRadius = Math.hypot(Math.max(x, innerWidth - x), Math.max(y, innerHeight - y));
const transition = document.startViewTransition(async () => {
isDark.value = !isDark.value;
await nextTick();
});
transition.ready.then(() => {
const clipPath = [`circle(0px at ${x}px ${y}px)`, `circle(${endRadius}px at ${x}px ${y}px)`];
document.documentElement.animate(
{
clipPath: isDark.value ? [...clipPath].reverse() : clipPath,
},
{
duration: 400,
easing: "ease-out",
pseudoElement: isDark.value ? "::view-transition-old(root)" : "::view-transition-new(root)",
},
);
});
}
用 JS 自定义过渡动画时,需要先覆盖默认的 CSS 动画
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
}
具体效果查看下图或前往 antfu 尝试

这样就实现了一个用 View Transitions API 做的主题切换。
总结
调用 document.startViewTransition 后,浏览器会获取当前页面状态(类似截图),DOM 变化后再记录变化后的状态,然后触发过渡动画,像透明度、位移变化等;能自定义 CSS 动画,默认是整个页面淡入淡出。
::view-transition-old 代表旧视图,::view-transition-new 代表新视图。
可给元素指定 view-transition-name 来明确具体元素的变化,但同一时间页面不能有两个相同 view-transition-name 的元素,否则视图变化失效。
视图转换动画是体验增强功能,不是必要的。动画会拖慢页面打开或更新速度,动画期间页面完全"冻结",所以要衡量好动画时间,页面本身慢就别用。
参考
View Transitions API - Web API | MDN
Document:startViewTransition() 方法 - Web API | MDN
太丝滑了!了解一下原生的视图转换动画 View Transitions - 前端侦探 - SegmentFault 思否