View Transition API:让网页切换不再像翻书

一、它是什么?

你用过手机上的 App 吧?从一个页面滑到另一个页面,图片从缩略图放大成全屏,导航栏稳稳地待在那儿不动——一切连贯流畅,你的眼睛不需要重新"找"东西在哪里。

但在网页上呢?点击一个链接,白屏一闪,新内容突然就出现了。就像翻漫画——啪一下,下一页突然就在眼前。你的大脑得重新定位:"标题在哪?导航在哪?"

View Transition API 就是来解决这个问题的。

它让浏览器在视图切换时自动创建平滑的过渡动画——旧的东西淡出,新的东西淡入,特定元素甚至可以从旧位置滑到新位置。一句话:让网页的状态切换有"电影感",而不是"PPT感"。

底层原理

先在大脑里播放一下这个"电影"是怎么拍出来的:

  1. 浏览器拍一张旧界面的快照(就像拍照片)
  2. 执行你的 DOM 更新(界面真的变了)
  3. 浏览器再拍一张新界面的快照
  4. 两张快照叠在一起,用 CSS 动画让旧淡出、新淡入
  5. 动画结束,扔掉快照,你看到的就是真实的新界面

没有魔法,就是两张快照叠在一起做动画。理解了这个物理图像,后面所有的 CSS 伪元素和 JavaScript API 都只是对这个过程的控制点。


二、为什么用它?

过去在 SPA 里做视图切换动画,你得自己:管理新旧内容并存、定位、写动画代码、清理旧 DOM、处理无障碍问题(屏幕阅读器困惑、焦点丢失、阅读位置跳)。几百行代码,容易出 bug。而 MPA 跨页面过渡——以前根本做不到,页面一导航旧页面就没了。

过去 现在
自己管理新旧内容并存 浏览器自动拍快照,新旧状态天然并存
自己写动画代码 默认交叉淡入淡出,零代码即可生效
MPA 跨页面过渡不可能 一行 CSS 就能启用
无障碍问题要自己处理 API 内置处理,避免焦点混乱
动画期间用户误触旧内容 旧快照是静态图像,不会误触

它不是给你一个"更强大的动画库"——而是把过渡动画的基础设施交给浏览器。你只需要说"我要过渡",其余的浏览器搞定。

但它不是万能的:核心是基于快照的 2D 过渡,适合元素滑移、缩放、淡入淡出这类效果。如果你需要 3D 变换、粒子效果、物理模拟,它不是最好的工具。


三、怎么用?

SPA 过渡(同文档)—— 5 分钟上手

function switchView() {
  // 特性检测:不支持就正常更新,只是没动画
  if (!document.startViewTransition) {
    updateTheDOMSomehow();
    return;
  }

  // 用 View Transition API 包裹 DOM 更新
  document.startViewTransition(() => updateTheDOMSomehow());
}

把 DOM 更新包在 startViewTransition() 回调里。浏览器会拍旧快照、执行回调、拍新快照、自动做交叉淡入淡出。零 CSS。

特性检测那行不是装饰——它确保不支持 API 的浏览器仍正常工作。这是渐进增强,不是功能依赖。

MPA 过渡(跨文档)—— 一行 CSS

@view-transition {
  navigation: auto;
}

两个页面(源和目标)都加这行,同源导航时自动过渡。零 JS,零 DOM 操作。 目前仅限同源页面。

自定义动画速度

::view-transition-group(root) {
  animation-duration: 0.5s;
}

推荐在 ::view-transition-group() 上设置而非 old/new,因为 group 样式会被继承,避免层级间时长不一致。

给特定元素独立动画

默认整页一起淡入淡出。但很多时候你不想这样——导航栏应该不动,只有内容区域切换;图片应该从缩略图位置滑到全屏位置。

view-transition-name 把元素分离出来:

figcaption {
  view-transition-name: figure-caption;
}

伪元素树变成:

::view-transition
  ├─ ::view-transition-group(root)              ← 页面其余部分
  │  └─ ::view-transition-image-pair(root)
  │     ├─ ::view-transition-old(root)
  │     └─ ::view-transition-new(root)
  └─ ::view-transition-group(figure-caption)    ← figcaption 独立动画
     └─ ::view-transition-image-pair(figure-caption)
        ├─ ::view-transition-old(figure-caption)
        └─ ::view-transition-new(figure-caption)

然后分别设置动画:

::view-transition-old(figure-caption) {
  animation: shrink-x 0.25s ease-in forwards;
}
::view-transition-new(figure-caption) {
  animation: grow-x 0.25s ease-out forwards;
}

@keyframes shrink-x { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@keyframes grow-x   { from { transform: scaleX(0); } to { transform: scaleX(1); } }

更聪明的做法:如果你只是想让元素从旧尺寸过渡到新尺寸,不用自己写动画——浏览器默认就会对 width/height 做平滑缩放,设个固定高度即可:

figcaption {
  view-transition-name: figure-caption;
  height: 30px;
}

各伪元素的作用:

伪元素 是什么 你能做什么
::view-transition 覆盖层根,盖在页面内容之上 设置背景色等
::view-transition-group() 快照容器 控制位置、尺寸、时长
::view-transition-image-pair() 旧/新快照容器 控制混合模式
::view-transition-old() 旧视图静态快照 自定义淡出动画
::view-transition-new() 新视图实时快照(可交互) 自定义淡入动画

高级:圆形揭示动画

原理一样,只是把默认淡入淡出换成从点击位置扩散的圆形揭示。关掉默认动画,用 Web Animations API:

::view-transition-old(root),
::view-transition-new(root) {
  animation: none;
  mix-blend-mode: normal;
}
::view-transition-old(root) { z-index: 1; }
::view-transition-new(root) { z-index: 9999; }
document.startViewTransition(() => updateDOM()).ready.then(() => {
  const x = event.clientX;
  const y = event.clientY;
  const endRadius = Math.hypot(
    Math.max(x, innerWidth - x),
    Math.max(y, innerHeight - y)
  );

  document.documentElement.animate(
    {
      clipPath: [
        `circle(0px at ${x}px ${y}px)`,
        `circle(${endRadius}px at ${x}px ${y}px)`,
      ],
    },
    {
      duration: 500,
      easing: 'ease-in-out',
      pseudoElement: '::view-transition-new(root)',
    }
  );
});

关键:.ready Promise 完成时伪元素树已建好,是 JS 插手改动画的时机。用 clipPath 从小圆变大圆——像从点击位置炸开。

ViewTransition 对象生命周期

startViewTransition() 返回的对象有三个 Promise:

Promise 完成时机 用途
updateCallbackDone DOM 更新回调完毕 确认 DOM 已更新
ready 伪元素树已建好,动画即将开始 用 Web Animations API 替换默认动画
finished 动画结束,新界面完全可见 清理临时状态

还有 skipTransition() 可中途取消动画。

MPA 进阶:pageswap / pagereveal 事件

跨文档过渡没有 JS 触发 API,但可通过两个事件插手——出站页面用 pageswap,入站页面用 pagereveal,分别给对应元素设置 view-transition-name 来实现"共享元素"过渡:

// 出站页面:给被点击的链接设名字
window.addEventListener('pageswap', (event) => {
  if (!event.viewTransition) return;
  const clickedLink = document.querySelector(`a[href="${toURL.pathname}"]`);
  if (clickedLink) clickedLink.style.viewTransitionName = 'profile-image';

  // 快照拍完后必须清除!
  event.viewTransition.finished.then(() => {
    if (clickedLink) clickedLink.style.viewTransitionName = '';
  });
});

// 入站页面:给目标元素设同样的名字
window.addEventListener('pagereveal', (event) => {
  if (!event.viewTransition) return;
  const img = document.querySelector('.profile-image');
  if (img) img.style.viewTransitionName = 'profile-image';

  event.viewTransition.finished.then(() => {
    if (img) img.style.viewTransitionName = '';
  });
});

踩坑提醒:快照拍完后必须清除 view-transition-name。不清除的话,名字会留在 bfcache 中,用户按返回按钮时 pagereveal 可能给另一个元素设了同样的名字——两个元素同名,过渡直接被跳过。


四、兼容性

SPA 同文档过渡 —— Baseline Newly Available

2025 年 10 月 14 日,随着 Firefox 144 发布,同文档视图过渡在三大引擎全部支持。

浏览器 最低版本 状态
Chrome / Edge 111
Safari 18
Firefox 144
Samsung Internet 22

加上特性检测可安全使用。注意 Firefox 144 暂不支持 view transition types。

MPA 跨文档过渡 —— 仍在演进

浏览器 最低版本 状态
Chrome / Edge 126
Safari 18.2
Firefox ❌ 不支持

Firefox 尚未实现跨文档过渡,是 Interop 2025 推进目标之一。

各子特性速查

特性 Chrome Safari Firefox
startViewTransition() 111+ 18+ 144+
@view-transition 规则 126+ 18.2+
view-transition-name 111+ 18+ 144+
view-transition-class 125+ 18.2+ 144+
:active-view-transition 125+ 18.2+ 144+
pageswap / pagereveal 126+ 18.2+
element.startViewTransition() 128+

实际建议

  • SPA 项目:放心用,三大浏览器全支持,降级路径清晰
  • MPA 项目:Chrome/Safari 可用,Firefox 不支持。可在 MPA 中混合使用——页面内状态变化用 SPA 方式,页面间导航等 Firefox 支持后再启用
  • 核心原则:渐进增强。不支持就正常工作没动画,你大胆用,出错只是回到"没动画"状态

总结

回到那个物理图像:浏览器拍两张快照,叠在一起做动画。

所有功能都是对这个过程的控制点:

  • document.startViewTransition() → 告诉浏览器"开始拍"
  • view-transition-name → 告诉浏览器"这个元素单独拍"
  • ::view-transition-old/new() → 控制两张快照怎么动
  • @view-transition { navigation: auto } → 告诉浏览器"页面导航时自动拍"

它不是动画库,是基础设施级别的改变——就像 CSS 让你不再用 JS 管样式一样,View Transition API 让你不再用 JS 管视图切换动画。最直接的解决方案,往往就是最好的。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容