告别路由动画库:用View Transitions API为SPA实现原生级页面切换

单页应用的页面切换动画一直是前端体验里很重要的一环。过去我们要么用 CSS Transition 配合路由库的生命周期自己写,要么引入像 Animate.css 或 Motion One 这样的库。但无论哪种方案,都得手动管理两个页面(或组件)的进入和离开状态,代码一多就容易出问题。Chrome 111 在 2023 年 3 月正式支持了 View Transitions API,到 2025 年,这个特性已经非常稳定,Safari 和 Firefox 也在跟进。它让你用几行 JavaScript 就能实现类似原生应用的页面过渡效果,连截图和动画都由浏览器自己搞定。这篇文章带你把它的基本用法摸透,然后写一个完整的多页面切换示例。

一、我们先看一下旧方案有多累

假设你有一个简单的 SPA,通过点击导航链接切换页面。传统的实现思路是这样的:点击链接时,先给当前页面添加一个退出动画,动画结束后销毁当前页面,同时创建新页面并设置一个进入动画。这背后需要你维护动画状态、计算时间、处理中断。如果你用的不是 Vue 的 <Transition> 或 React 的 framer-motion,纯手写的话,很容易出现动画不同步、闪烁,或者用户在动画期间点击其他链接导致状态错乱。

View Transitions API 换了一种角度。你只需要告诉浏览器”我要更新 DOM 了”,然后浏览器会自动完成以下步骤:

  1. 给当前页面拍一张快照。
  2. 执行你的 DOM 更新逻辑。
  3. 给更新后的页面再拍一张快照。
  4. 用旧快照和新快照自动生成平滑的过渡动画。

你不需要手动操作任何动画属性。那个感觉就像在原生应用里 push 了一个新页面——默认会有一个淡入淡出或滑动的效果。

二、最简单的例子:点击按钮,文本切换带过渡

我们从一个极简的 demo 开始,感受一下这个 API 的魔力。下面的 HTML 里只有一个标题和一个按钮,点击按钮后标题文字会变,但变化过程是平滑的交叉淡入淡出。

<h1 id="title">Hello World</h1>
<button id="changeBtn">更换文字</button>

<script>
  const titleEl = document.getElementById('title');
  const btn = document.getElementById('changeBtn');

  btn.addEventListener('click', () => {
    // 判断浏览器是否支持
    if (!document.startViewTransition) {
      titleEl.textContent = '你好,世界';
      return;
    }

    // 核心:把 DOM 更新放进回调里
    document.startViewTransition(() => {
      titleEl.textContent = '你好,世界';
    });
  });
</script>

在支持 View Transitions API 的浏览器里点击按钮,你会看到旧文字淡出,新文字淡入。就这么简单。如果浏览器不支持,document.startViewTransition 是 undefined,代码会直接更新 DOM,不会报错。所以它天然就是渐进增强的。

这个默认的过渡效果是全局的:浏览器会截取整个视口,然后做交叉淡入淡出。对于文字这种小范围变化,整个页面淡入淡出有点太重了。好在 API 允许我们限定过渡的范围,还要自定义动画。

三、限定过渡范围和自定义动画

浏览器在执行视图过渡时,会自动为快照中的每个元素(如果有变化)生成一组伪元素:

  • ::view-transition-old(root) —— 旧状态的根快照
  • ::view-transition-new(root) —— 新状态的根快照

你可以通过给想要参与过渡的元素设置 view-transition-name 属性,来让浏览器只为这个特定元素创建过渡快照,而不是整页。然后就可以在 CSS 里用这些伪元素来定制动画。

3.1 给元素命名

<h1 id="title" style="view-transition-name: my-title;">Hello World</h1>

注意:view-transition-name 的值在整个页面里必须是唯一的。如果有两个元素同时用了同一个名字,浏览器会抛异常。

3.2 在 CSS 里自定义动画

::view-transition-old(my-title) {
  animation: fade-out 0.3s ease-out forwards;
}

::view-transition-new(my-title) {
  animation: fade-in 0.3s ease-out forwards;
}

@keyframes fade-out {
  from { opacity: 1; }
  to { opacity: 0; }
}

@keyframes fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

这样一来,只有标题元素会参与过渡,背景和其他内容保持不变。动画的时机和形式完全由你控制。

四、实战:多页面切换的 SPA 过渡

上面的例子只是换了一行字。真实场景里,页面切换往往伴随着多个元素的变化:标题变了、内容区整个都不同了、导航高亮也变了。我们把 View Transitions API 用在一个简单的多页面路由上。

4.1 项目结构

我们要做一个包含首页、列表页和详情页的单页应用。点击顶部导航切换页面,切换时旧页面内容区淡出并向左平移,新页面内容区从右侧滑入并淡入。标题和底部栏保持不变。

4.2 HTML 骨架

<nav>
  <button data-route="home" class="nav-btn active">首页</button>
  <button data-route="list" class="nav-btn">列表</button>
  <button data-route="detail" class="nav-btn">详情</button>
</nav>

<main id="app" class="view-root">
  <div class="page" id="home">
    <h2>欢迎来到首页</h2>
    <p>这里是最新的内容推荐。</p>
  </div>
  <div class="page" id="list" style="display:none;">
    <h2>列表页</h2>
    <ul>
      <li>文章 A</li>
      <li>文章 B</li>
      <li>文章 C</li>
    </ul>
  </div>
  <div class="page" id="detail" style="display:none;">
    <h2>详情页</h2>
    <p>这里是文章的详细内容。</p>
  </div>
</main>

4.3 核心 JavaScript 路由器

const navButtons = document.querySelectorAll('.nav-btn');
const pages = {
  home: document.getElementById('home'),
  list: document.getElementById('list'),
  detail: document.getElementById('detail'),
};
let currentRoute = 'home';

// 切换路由的函数
function navigateTo(route) {
  if (route === currentRoute) return;

  // 更新按钮高亮
  navButtons.forEach(btn => {
    btn.classList.toggle('active', btn.dataset.route === route);
  });

  // 如果浏览器不支持 View Transitions,直接切换
  if (!document.startViewTransition) {
    switchPage(route);
    return;
  }

  // 启动视图过渡
  const transition = document.startViewTransition(() => {
    switchPage(route);
  });

  // 可以接收 transition.finished 来做过渡完成后的处理(这里暂时不用)
}

function switchPage(route) {
  // 隐藏所有页面
  Object.values(pages).forEach(p => p.style.display = 'none');
  // 显示目标页面
  pages[route].style.display = 'block';
  currentRoute = route;
}

// 绑定导航事件
navButtons.forEach(btn => {
  btn.addEventListener('click', () => {
    navigateTo(btn.dataset.route);
  });
});

4.4 配合 CSS 定义页面过渡动画

为了让页面内容区有滑动效果,我们需要给容器(main)一个 view-transition-name,并定义对应的旧/新快照动画。特别要注意,每次页面切换时,因为容器内容变了,浏览器会为这个容器创建过渡快照。我们在 CSS 里这样写:

/* 给内容区一个过渡名称 */
.view-root {
  view-transition-name: main-content;
}

/* 旧内容:向左滑出并淡出 */
::view-transition-old(main-content) {
  animation: slide-out 0.3s ease-in forwards;
}

/* 新内容:从右侧滑入并淡入 */
::view-transition-new(main-content) {
  animation: slide-in 0.3s ease-out forwards;
}

@keyframes slide-out {
  from {
    transform: translateX(0);
    opacity: 1;
  }
  to {
    transform: translateX(-30px);
    opacity: 0;
  }
}

@keyframes slide-in {
  from {
    transform: translateX(30px);
    opacity: 0;
  }
  to {
    transform: translateX(0);
    opacity: 1;
  }
}

现在点击顶部导航按钮,你会看到内容区像原生应用一样滑动切换。标题和底部栏因为没有设置 view-transition-name,所以不受影响,保持不动。整个过程中我们没有手动写任何动画启动或关闭的逻辑——切换页面就一行 document.startViewTransition

五、踩坑和需要注意的细节

当我自己第一次在生产项目里用这个 API 时,遇到了几个容易忽略的点:

1. 同一名称必须全局唯一。如果页面中有两个元素同时使用了相同的 view-transition-name,调用 startViewTransition 会立即抛出 DOMException。所以当你用组件化开发时,要保证动态渲染的元素不会命名冲突。一个常用技巧是:只有在切换动画期间才给元素添加 view-transition-name,或者使用路由相关的 key 来动态生成名称。

2. 页面快照是静态位图。::view-transition-old 实际上是一张静态截图,过渡期间它不会再响应 DOM 变化。如果你的旧页面里有 gif 动画或视频,过渡时会冻结在旧状态。这是特意设计的,目的是保证过渡的平滑和性能。如果你不希望冻结,就得考虑其他方式。

3. 过渡期间可以继续交互吗?默认情况下,视图过渡进行时页面仍可交互,但某些操作(比如再次启动一个新的视图过渡)会被跳过,直到当前过渡完成。浏览器提供了一个 skipTransition() 方法可以立即结束过渡,但最好等待自然完成。

4. 跨页面过渡(MPA)也支持。Chrome 126+ 开始支持跨文档视图过渡,即从页面 A 导航到页面 B(非 SPA)也能有平滑动画。需要在两端的页面头部添加 <meta name="view-transition" content="same-origin">。这个特性目前还比较新,Safari 和 Firefox 还未实现,但方向已经明确。

六、和框架集成的一点点思路

在 Vue 或 React 中,你可以把视图过渡的启动位置放在路由守卫或者状态更新的回调里。比如 Vue Router 的 beforeEach 里不适合直接调用 startViewTransition,因为这时 DOM 还没更新。正确的做法是在组件内部,当你改变响应式数据来展示新页面内容时,用 nextTick 或者 flushSync(React)确保 DOM 更新发生在视图过渡的回调里。

一个通用的封装思路是:创建一个自定义 hook 或组合式函数,把 startViewTransition 包裹起来,并处理命名冲突和动画定义。这能让团队在不同页面间复用一致的过渡效果。

七、总结

View Transitions API 把页面切换动画从”手写状态机”降维到了”声明意图”。你不再需要关心旧页面什么时候销毁、新页面什么时候挂载,只需要在 DOM 更新之前告诉浏览器一声,剩下的截图、混合、动画执行都由浏览器底层处理。默认效果已经足够好,自定义的灵活度也完全不输第三方库。

对于还在用各种动画库处理路由过渡的项目,可以逐步尝试用这个原生 API 替代。它体积为零,性能更好,而且随着浏览器支持面的扩大,将来会成为 SPA 转场的标配手段。文中的代码在 Chrome 126+ 上都是直接可运行的,不妨复制一份到你的本地项目里试试——那种旧页面滑出、新页面滑入的丝滑感,会让你瞬间忘记以前为了加个动画写了多少行代码。

告别路由动画库:用View Transitions API为SPA实现原生级页面切换
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

版权声明:
本站资源有的来自互联网收集整理,本站纯免费分享提供学习使用,如果侵犯了您的合法权益,请联系本站我们会及时删除。
本站资源仅供研究、学习交流之用,免费开源项目不代表完全可商用,若商业用途请先咨询开发企业能否商用,否则产生的一切后果将由下载用户自行承担。
原创板块未经允许不得转载,否则将追究法律责任。

淘吗网 javascript 告别路由动画库:用View Transitions API为SPA实现原生级页面切换 https://www.taomawang.com/web/javascript/2380.html

下一篇:

已经没有下一篇了!

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务