
最近在优化自己的一个 Next.js 项目时,又重新关注到了一个以前经常被我忽略的细节:
页面切换时的过渡体验。
很多时候我们讨论性能优化,会关注首屏时间、LCP、TTFB、缓存策略等等指标。
这些当然重要。
但从用户视角来看,他们感知到的「流畅」,其实不完全来自性能数据。
有时候只是因为页面切换时多了一个自然的过渡动画。
这种感觉很微妙。
页面可能没有变快,但用户会觉得整个应用更顺滑、更高级。
而这次让我重新关注这个问题的原因,是因为我想在 Next.js 里实现 Nuxt 那种我一直很喜欢的页面切换效果。
因为我平时分享的大部分内容都是 Next.js,所以关注我的朋友里可能有些人对 Nuxt 不太熟悉。(我之前是写Nuxt的)
简单来说:
Vue 对应 React
Nuxt 对应 Next
Nuxt 是 Vue 生态里非常成熟的全栈框架。
除了 SSR、SSG、文件路由这些能力之外,它在开发体验和用户体验方面也做了很多不错的设计。
其中有一个我一直很喜欢的功能:
Page Transition(页面切换动画)。
第一次接触 Nuxt 的时候,我其实并没有特别在意这个功能。
后来项目做多了之后才发现,这种细节真的很容易提升整个产品的质感。
大家可以回忆一下很多网站的跳转过程。
点击一个链接。
当前页面瞬间消失。
新页面瞬间出现。
整个过程就像:
页面A -> 啪 -> 页面B
虽然速度很快。
但视觉上其实是断裂的。
尤其是在内容比较丰富的页面里,这种突然切换会让用户产生一种被打断的感觉。
而 Nuxt 的页面切换逻辑则更像这样:
页面A
↓
退出动画
↓
进入动画
↓
页面B
用户能够明确感知到:
我正在从一个页面进入另一个页面。
这种连续性会让整个应用显得更加自然。
以前刚开始写前端的时候,我其实不太在意这些东西。
总觉得:
动画嘛。
属于锦上添花。
功能能跑就行。
后来慢慢发现并不是这样。
很多优秀产品的体验差异,恰恰来自这些细节。
用户的大脑其实需要时间理解界面变化。
如果页面瞬间替换。
视觉焦点会突然丢失。
而过渡动画能够帮助用户建立上下文。
让变化过程变得连续。
这是我后来感触特别深的一点。
有时候接口响应时间并没有变化。
甚至页面加载时间也没有变化。
但只要加入合理的过渡动画。
用户就会觉得:
好像变快了。
因为等待过程被包装得更加自然。
这也是为什么很多成熟产品都会在加载和切换阶段加入动画反馈。
很多时候用户说不出来哪里好。
但就是会觉得:
这个产品用起来挺舒服。
这种舒服感其实来自无数细节的叠加。
按钮反馈。
滚动体验。
加载状态。
页面切换。
这些东西单独看都不重要。
但组合起来就会形成明显差异。
我第一次认真研究 Nuxt 的页面切换时,最大的感受是:
它把这件事情做得非常自然。
开发者只需要简单配置。
就能获得完整的页面过渡能力。
而且整个实现方式和路由系统结合得很好。
很多时候甚至不需要额外思考。
这也是为什么后来回到 Next.js 项目时,我总觉得少了点什么。
于是就想着:
能不能把这种体验也搬过来?
研究了一圈之后发现。
其实实现起来并不复杂。
核心依赖是 Motion(原 Framer Motion)。
整体思路非常简单:
获取当前路由路径。
const path = usePathname()
把 pathname 作为组件 key。
这样当路由变化时。
React 会认为这是一个新的组件实例。
从而触发卸载和重新挂载。
利用 AnimatePresence 捕获组件退出阶段。
这样旧页面不会立刻消失。
而是先执行退出动画。
新页面执行进入动画。
最终形成完整的页面切换效果。
直接放代码。
我目前项目里就是这么写的。
'use client'
import { AnimatePresence, motion } from 'motion/react'
import { usePathname } from 'next/navigation'
import { type ReactNode, useEffect } from 'react'
const RootTemplate = ({ children }: { children: ReactNode }) => {
const path = usePathname()
useEffect(() => {
navigator?.serviceWorker?.register('/sw.js', {
scope: '/',
updateViaCache: 'none',
})
}, [])
return (
<AnimatePresence mode="popLayout">
<motion.div
animate={{ opacity: 1 }}
className="h-full"
exit={{
opacity: 0,
transition: {
ease: 'easeIn',
},
}}
initial={{ opacity: 0 }}
key={path}
transition={{
duration: 0.6,
ease: 'easeOut',
}}
>
{children}
</motion.div>
</AnimatePresence>
)
}
export default RootTemplate
刚开始尝试的时候。
我其实测试过很多效果。
比如:
位移动画
缩放动画
滑动切换
卡片翻页
组合动画
效果都挺酷。
但实际放到业务项目里之后发现一个问题。
太抢戏了。
用户每切一次页面都要看一次动画。
时间长了反而会觉得烦。
尤其是后台系统和内容网站。
频繁切换页面的时候会特别明显。
后来我把所有复杂效果都删掉了。
只保留最简单的透明度变化。
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
结果反而更舒服。
用户能感知到页面正在切换。
但不会注意到动画本身。
我觉得这是一个比较理想的状态。
上线之后我自己用了几天。
最大的感受是:
整个应用突然变得“连贯”了。
以前切换页面的时候会有一种:
A页面
↓
断开
↓
B页面
的感觉。
现在则更像:
A页面
↓
过渡
↓
B页面
虽然只是一个简单的透明度动画。
但整体体验提升比我预想的大很多。
甚至有朋友在体验项目的时候主动提到:
感觉页面切换挺舒服的。
而实际上他们根本不知道背后用了什么技术。
以前总觉得前端最重要的是架构、性能、工程化。
现在依然觉得这些很重要。
但随着项目做得越来越多,我越来越相信一件事:
真正决定用户体验的,往往不是那些宏大的技术方案。
而是无数个微小细节。
页面切换动画就是其中之一。
它不会让你的项目性能翻倍。
不会让 Lighthouse 多拿几十分。
甚至很多用户都不会主动注意到它。
但它会让整个应用显得更加流畅、自然、有质感。
如果你平时主要写 Next.js,又刚好觉得页面切换有点生硬。
不妨试试看。
我自己实践下来,确实有一种把 Nuxt 那种舒服体验搬过来的感觉。
![[鸣潮·共鸣与群星_捏捏]](https://i0.hdslb.com/bfs/garb/item/da81b7cf658330c7ae6aa4ad59276e6d7b4e48c3.png)