写了八年 MPA,今天终于不用靠 Barba.js 过日子了——View Transitions Level 2 把跨文档过渡做进了浏览器底层
以前做 MPA 页面跳转过渡,要么套一层 Barba.js,要么自己监听 pageshow + history.pushState 配一套动画逻辑。写了七八年代码,跳转动画这块始终没找到一个干净的原生方案。Chrome 126 开始,View Transitions Level 2 把跨文档过渡做进了浏览器底层——两个页面都声明参与,浏览器自己截快照、跑动画、接管渲染时机,连 JS 调用都不用写。
以前的坑:MPA 跳转,DOM 状态全丢了
MPA 的根本问题是:每次跳转,浏览器会把旧页面整个卸载掉,DOM 清空,JS 上下文销毁。想做过渡动画,只能靠「旧页面截个图→跳转→新页面截个图→叠加两层做 crossfade」这套 hack。Barba.js 就是这么干的——它接管你的跳转,把上一个页面藏起来做快照,等新页面加载完再跑动画。
问题是:这套方案重、兼容性坑多、SEO 不友好,还要多装一个库。
View Transitions Level 2 解决了什么
Level 2 最大的变化是把「同文档内过渡」扩展到了「跨文档跳转」,而且是声明式自动触发,不用 JS 调 API。
核心新增了三个东西:
1. Cross-document view transitions(同源自动触发)
当两个页面都声明参与同一个过渡,浏览器会在跳转时自动完成:
- 旧页面 DOM 冻结,截取快照
- 新页面渲染第一帧之前,旧快照和新画面叠加
- 按
::view-transition-old/new的 CSS 动画执行过渡
触发条件:同源跳转 + 两边都 opt-in + 没有跨域重定向。
2. NavigationActivation API(Baseline 2026)
// 读取当前导航的上下文——从哪来、到哪去、什么类型
window.addEventListener("pagereveal", (e) => {
if (!navigation.activation.from) return;
const from = navigation.activation.from.url;
const to = navigation.activation.entry.url;
const type = navigation.activation.navigationType; // navigate/reload/back/forward
// 知道了从哪到哪,可以给不同的跳转配不同的过渡名
if (new URL(from).pathname.startsWith("/product")) {
document.startViewTransition(() => {
// 定向到详情页的过渡动画逻辑
});
}
});
这个 API 通过 window.navigation.activation 和 PageSwapEvent.activation 暴露,替代了以前自己监听 popstate + 手动维护来源 URL 那套方法。
3. PageSwapEvent.activation(Chrome 124+)
pageswap 事件现在带上了目标页面的激活信息:
window.addEventListener("pageswap", (e) => {
if (!e.activation) return;
const from = e.activation.from;
const to = e.activation.entry;
// 判断是前进还是后退,给不同的视图过渡
if (e.activation.navigationType === "back") {
document.querySelector("#page").dataset.transition = "slide-right";
} else {
document.querySelector("#page").dataset.transition = "slide-left";
}
});
怎么用:两行代码跑起来
第一步:两个页面都 opt-in
MPA 跨文档过渡需要两边页面都声明参与。方式有两种:
方式 A — CSS 声明式(推荐):
/* 两个页面都加上这段 */
@view-transition {
navigation: auto;
}
方式 B — JS 手动控制:
// 新页面加载完成之后调用
if (!document.startViewTransition) return;
document.documentElement.style.viewTransitionName = "page-root";
第二步:自定义过渡动画
/* 默认是 crossfade,想自定义就这样写 */
::view-transition-old(root) {
animation: 300ms ease-out both fade-out;
}
::view-transition-new(root) {
animation: 300ms ease-in both fade-in;
}
@keyframes fade-out {
from { opacity: 1; transform: translateX(0); }
to { opacity: 0; transform: translateX(-20px); }
}
@keyframes fade-in {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
第三步:按跳转类型配不同动画(NavigationActivation)
window.addEventListener("pagereveal", async (e) => {
if (!navigation.activation?.from) return;
// 后退/前进和全新导航配不同的过渡方向
const isBack = navigation.activation.navigationType === "back";
document.startViewTransition(() => {
// 更新 DOM
updateContent();
// 用 CSS 变量控制方向
document.documentElement.dataset.direction = isBack ? "back" : "forward";
});
});
[data-direction="back"] ::view-transition-old(root) {
animation: 300ms ease-out both slide-in-right;
}
[data-direction="back"] ::view-transition-new(root) {
animation: 300ms ease-in both slide-out-left;
}
View Transition Object(进阶)
Level 2 还引入了 View Transition Object,允许你在跳转前手动控制过渡的触发时机和快照内容:
// 手动触发,不依赖 CSS 的 view-transition: auto
const transition = document.startViewTransition(() => {
updateSomeDOM();
}, {
activation: navigation.activation, // 关联当前导航上下文
types: ["product-detail"], // 给这个过渡打标签
});
// 快照截取完成后触发
transition.ready.then(() => {
// 旧快照已就位,新 DOM 正在渲染,可以在这里加额外的 JS 动画
});
这在做「共享元素动画」(比如列表点击后,列表项图片飞进详情页大图)时特别有用——你可以给源元素和目标元素打上相同的 view-transition-name,浏览器会自动把它们匹配起来做缩放位移动画:
/* 列表页图片 */
.product-thumb {
view-transition-name: product-image;
}
/* 详情页大图 */
.product-hero {
view-transition-name: product-image; /* 相同名字,自动配对 */
}
兼容性:谁已经能用了
| 特性 | Chrome | Safari | Firefox |
|---|---|---|---|
| Cross-document VT | 126+ | ❌ | ❌ |
| NavigationActivation | 126+ (Baseline 2026) | ❌ | ❌ |
| PageSwapEvent.activation | 124+ | ❌ | ❌ |
| View Transition Object | Flag (M123) | ❌ | ❌ |
CSS view-transition: auto |
117+ | 18+ | 129+ |
Safari 目前只支持同文档内过渡,Firefox 支持有限。生产环境用之前记得先 if (!document.startViewTransition) 做能力检测。
迁移思路:从 Barba.js 切过来
如果项目已经在用 Barba.js 或者类似的 MPA 过渡库,迁移可以分三步走:
- 保留旧方案做兜底 — 先在两个页面都加上
@view-transition { navigation: auto; },观察浏览器行为 - 替换不需要共享元素的过渡 — 比如全屏 crossfade、滑动切换这类通用动画,直接删掉 Barba.js 的配置,用 CSS 接管
- 用 NavigationActivation 替换手动 URL 判断 — 把以前自己维护的「从哪个 URL 跳到哪个 URL」逻辑换成
navigation.activation.from/entry
共享元素动画这块可以留着 Barba.js,等 View Transition Object 脱离 Flag 再迁。
下一步
现在你有两个选择:
- SPA 应用:
document.startViewTransition()同文档过渡已经稳定,Safari 18+ 也支持了,直接用 - MPA 应用:先把
@view-transition { navigation: auto; }加上,跑一个 crossfade 看看效果
MDN 文档:https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API
W3C 规范:https://www.w3.org/TR/css-view-transitions-2/
Chrome Status(View Transition Object):https://chromestatus.com/feature/5076557983121408
评论区
登录后可评论。