我把 Nuxt 的页面切换动画搬到了 Next.js 里
aznlux
2026年06月11日 20:20

最近在优化自己的一个 Next.js 项目时,又重新关注到了一个以前经常被我忽略的细节:

页面切换时的过渡体验。

很多时候我们讨论性能优化,会关注首屏时间、LCP、TTFB、缓存策略等等指标。

这些当然重要。

但从用户视角来看,他们感知到的「流畅」,其实不完全来自性能数据。

有时候只是因为页面切换时多了一个自然的过渡动画。

这种感觉很微妙。

页面可能没有变快,但用户会觉得整个应用更顺滑、更高级。

而这次让我重新关注这个问题的原因,是因为我想在 Next.js 里实现 Nuxt 那种我一直很喜欢的页面切换效果。


先简单聊聊 Nuxt

因为我平时分享的大部分内容都是 Next.js,所以关注我的朋友里可能有些人对 Nuxt 不太熟悉。(我之前是写Nuxt的)

简单来说:

  • Vue 对应 React

  • Nuxt 对应 Next

Nuxt 是 Vue 生态里非常成熟的全栈框架。

除了 SSR、SSG、文件路由这些能力之外,它在开发体验和用户体验方面也做了很多不错的设计。

其中有一个我一直很喜欢的功能:

Page Transition(页面切换动画)。

第一次接触 Nuxt 的时候,我其实并没有特别在意这个功能。

后来项目做多了之后才发现,这种细节真的很容易提升整个产品的质感。


大部分网站的页面切换其实很“生硬”

大家可以回忆一下很多网站的跳转过程。

点击一个链接。

当前页面瞬间消失。

新页面瞬间出现。

整个过程就像:

代码块
PlainText
自动换行
复制代码
页面A -> 啪 -> 页面B
复制成功

虽然速度很快。

但视觉上其实是断裂的。

尤其是在内容比较丰富的页面里,这种突然切换会让用户产生一种被打断的感觉。

而 Nuxt 的页面切换逻辑则更像这样:

代码块
PlainText
自动换行
复制代码
页面A
 ↓
退出动画
 ↓
进入动画
 ↓
页面B
复制成功

用户能够明确感知到:

我正在从一个页面进入另一个页面。

这种连续性会让整个应用显得更加自然。


为什么我觉得页面切换动画很有价值

以前刚开始写前端的时候,我其实不太在意这些东西。

总觉得:

动画嘛。

属于锦上添花。

功能能跑就行。

后来慢慢发现并不是这样。

很多优秀产品的体验差异,恰恰来自这些细节。

降低页面跳变感

用户的大脑其实需要时间理解界面变化。

如果页面瞬间替换。

视觉焦点会突然丢失。

而过渡动画能够帮助用户建立上下文。

让变化过程变得连续。


提升速度感知

这是我后来感触特别深的一点。

有时候接口响应时间并没有变化。

甚至页面加载时间也没有变化。

但只要加入合理的过渡动画。

用户就会觉得:

好像变快了。

因为等待过程被包装得更加自然。

这也是为什么很多成熟产品都会在加载和切换阶段加入动画反馈。


增强产品质感

很多时候用户说不出来哪里好。

但就是会觉得:

这个产品用起来挺舒服。

这种舒服感其实来自无数细节的叠加。

按钮反馈。

滚动体验。

加载状态。

页面切换。

这些东西单独看都不重要。

但组合起来就会形成明显差异。


Nuxt 为什么让我印象深刻

我第一次认真研究 Nuxt 的页面切换时,最大的感受是:

它把这件事情做得非常自然。

开发者只需要简单配置。

就能获得完整的页面过渡能力。

而且整个实现方式和路由系统结合得很好。

很多时候甚至不需要额外思考。

这也是为什么后来回到 Next.js 项目时,我总觉得少了点什么。

于是就想着:

能不能把这种体验也搬过来?


我在 Next.js 里的实现思路

研究了一圈之后发现。

其实实现起来并不复杂。

核心依赖是 Motion(原 Framer Motion)。

整体思路非常简单:

第一步

获取当前路由路径。

代码块
tsx
自动换行
复制代码
const path = usePathname()
复制成功

第二步

把 pathname 作为组件 key。

这样当路由变化时。

React 会认为这是一个新的组件实例。

从而触发卸载和重新挂载。


第三步

利用 AnimatePresence 捕获组件退出阶段。

这样旧页面不会立刻消失。

而是先执行退出动画。


第四步

新页面执行进入动画。

最终形成完整的页面切换效果。


我的完整实现代码

直接放代码。

我目前项目里就是这么写的。

代码块
tsx
自动换行
复制代码
'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
复制成功

为什么我最后只用了淡入淡出

刚开始尝试的时候。

我其实测试过很多效果。

比如:

  • 位移动画

  • 缩放动画

  • 滑动切换

  • 卡片翻页

  • 组合动画

效果都挺酷。

但实际放到业务项目里之后发现一个问题。

太抢戏了。

用户每切一次页面都要看一次动画。

时间长了反而会觉得烦。

尤其是后台系统和内容网站。

频繁切换页面的时候会特别明显。

后来我把所有复杂效果都删掉了。

只保留最简单的透明度变化。

代码块
tsx
自动换行
复制代码
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
复制成功

结果反而更舒服。

用户能感知到页面正在切换。

但不会注意到动画本身。

我觉得这是一个比较理想的状态。


实际使用后的感受

上线之后我自己用了几天。

最大的感受是:

整个应用突然变得“连贯”了。

以前切换页面的时候会有一种:

代码块
PlainText
自动换行
复制代码
A页面
↓
断开
↓
B页面
复制成功

的感觉。

现在则更像:

代码块
PlainText
自动换行
复制代码
A页面
↓
过渡
↓
B页面
复制成功

虽然只是一个简单的透明度动画。

但整体体验提升比我预想的大很多。

甚至有朋友在体验项目的时候主动提到:

感觉页面切换挺舒服的。

而实际上他们根本不知道背后用了什么技术。


最后

以前总觉得前端最重要的是架构、性能、工程化。

现在依然觉得这些很重要。

但随着项目做得越来越多,我越来越相信一件事:

真正决定用户体验的,往往不是那些宏大的技术方案。

而是无数个微小细节。

页面切换动画就是其中之一。

它不会让你的项目性能翻倍。

不会让 Lighthouse 多拿几十分。

甚至很多用户都不会主动注意到它。

但它会让整个应用显得更加流畅、自然、有质感。

如果你平时主要写 Next.js,又刚好觉得页面切换有点生硬。

不妨试试看。

我自己实践下来,确实有一种把 Nuxt 那种舒服体验搬过来的感觉。

[鸣潮·共鸣与群星_捏捏]