你为了页面切换动效装了一个几十 KB 的库——其实浏览器今天自带了,而且 GPU 加速、零依赖
你为了页面切换动效装了一个几十 KB 的库——其实浏览器今天自带了,而且 GPU 加速、零依赖
移动端做页面切换动效,你大概率装了 Framer Motion 或者 react-spring。AnimatePresence 套一层,variant 配一套,正反方向还要维护两套配置——光这个功能就要吞掉 30~50KB 的 JS。
这笔账在桌面端还能接受,但在移动端,用户的网络和 CPU 都不宽裕,bundle 多 1KB 用户就要多等几十毫秒。
2026 年,这件事有了原生解法。
View Transitions API 的路由版
在 SPA 里,路由切换本质是 DOM 状态的变更——旧页面消失,新页面出现。document.startViewTransition() 就是设计来干这件事的:
import { useNavigate } from 'react-router-dom';
function TransitionLink({ to, children }) {
const navigate = useNavigate();
const handleClick = (e) => {
e.preventDefault();
if (!document.startViewTransition) {
navigate(to);
return;
}
document.startViewTransition(() => navigate(to));
};
return <a href={to} onClick={handleClick}>{children}</a>;
}
这段代码对 React Router、Vue Router、Next.js App Router 都适用——只要你最后调用了路由更新,把更新动作包进 startViewTransition 的回调里就行。
浏览器会:1)截一张当前页面的快照,2)执行你的回调(更新 DOM),3)再截一张新页面快照,4)两张快照之间做交叉淡化(默认)。全程 GPU 合成,不需要布局重排。
双向滑动动画,三行 CSS
默认的淡入淡出已经很顺滑,但如果你的用户习惯了 App 的左滑右滑,可以自定义成滑动手势:
/* 前向导航:新页从右侧滑入 */
::view-transition-old(root) {
animation: 200ms ease-in both slide-to-left;
}
::view-transition-new(root) {
animation: 200ms ease-out both slide-from-right;
}
/* 后向导航:反过来 */
html[data-transition="back"] ::view-transition-old(root) {
animation: 200ms ease-in both slide-from-right;
}
html[data-transition="back"] ::view-transition-new(root) {
animation: 200ms ease-out both slide-to-left;
}
然后在点击时给 html 标签挂一个 data 属性来区分方向:
// 记录上一页栈深度,判断是前进还是后退
const depth = history.length;
document.startViewTransition(() => {
navigate(to);
});
document.documentElement.dataset.transition =
history.length > depth ? 'forward' : 'back';
比 Framer Motion 轻多少?
Framer Motion 的 page-transition 功能依赖 React 的 context 和动画状态机,在移动端浏览器里要跑两套机制(React reconciler + 动画引擎)。
View Transitions 是浏览器内置的,动画完全在合成器线程跑——主线程卡死都不影响动效流畅。caniuse 数据显示,2026 年中 same-document View Transitions(路由场景对应的那个)全球覆盖率约 89%,Chrome 111+、Safari 18+、Firefox 144+ 全部支持。
named transitions:让卡片自己动
Framer Motion 最吸引人的能力之一是 Shared Element Transition——列表页的卡片展开到详情页时,卡片自己飞过去。
View Transitions 也能做,而且不需要任何 JS:
/* 列表页和详情页里对应的卡片图片,共享同一个 view-transition-name */
.card-image {
view-transition-name: product-image;
contain: paint;
}
// 详情页
<img src={product.img} className="card-image" />
只要两边元素的 view-transition-name 相同,浏览器会自动把旧位置的元素 morph 到新位置——尺寸、圆角、位置全部自动插值。Framer Motion 里要写几十行配置的能力,原生三行 CSS 搞定。
落地步骤
第一步,封装一个跨框架的 hook:
// useViewTransitionNavigate.js
export function useViewTransitionNavigate() {
const navigate = useNavigate();
return (to, options) => {
if (!document.startViewTransition) {
navigate(to, options);
return;
}
document.startViewTransition(() => navigate(to, options));
};
}
第二步,把路由跳转的 Link 组件替换成这个 hook 的版本。框架无关,React/Vue/Svelte 都能用。
第三步,把 Framer Motion 的 AnimatePresence 相关代码全部删掉。先删再上线,观察用户反馈——大概率没人注意到差异,反而发现首屏快了几十毫秒。
什么场景别用
两个坑需要注意:
一是两端 DOM 结构差异太大的情况——比如列表页是 grid,详情页是完全不同的布局,named transition 会失效,此时退化到默认淡入淡出是合理的。
二是包含大量图片的页面——View Transitions 截的是位图快照,图片越多快照越慢。建议在路由跳转前预加载关键图片,或者对图片多的页面关掉 transition,等图片加载完再说。
移动端 SPA 的路由动效,这笔账 2026 年该重新算一算了。
评论区
登录后可评论。