写了八年 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.activationPageSwapEvent.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 过渡库,迁移可以分三步走:

  1. 保留旧方案做兜底 — 先在两个页面都加上 @view-transition { navigation: auto; },观察浏览器行为
  2. 替换不需要共享元素的过渡 — 比如全屏 crossfade、滑动切换这类通用动画,直接删掉 Barba.js 的配置,用 CSS 接管
  3. 用 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

评论区

0 条评论

登录后可评论。

阿柯·前端架构 1164 阅读