如何在NextJS中实现多路由页面间的组件滑动转场?
在Next.js中实现跨路由的滑动转场效果
要实现类似DigitalOcean那种两个独立路由间的向右滑动转场,核心是利用Next.js的路由事件监听+CSS动画,以下分两种路由方案给出实现步骤:
一、App Router(Next.js 13+)实现方式
1. 定义全局动画样式
在app/globals.css中添加转场动画的CSS规则:
/* 页面容器:确保占满视口,避免滚动干扰 */ .page-container { position: absolute; top: 0; left: 0; width: 100%; min-height: 100vh; } /* 从右侧滑入的进入动画 */ .slide-in-right { animation: slideInRight 0.3s ease-out forwards; } /* 向左侧滑出的离开动画 */ .slide-out-left { animation: slideOutLeft 0.3s ease-in forwards; } /* 反向路由的动画(从邮箱页返回首页) */ .slide-in-left { animation: slideInLeft 0.3s ease-out forwards; } .slide-out-right { animation: slideOutRight 0.3s ease-in forwards; } @keyframes slideInRight { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutLeft { from { transform: translateX(0); opacity: 1; } to { transform: translateX(-100%); opacity: 0; } } @keyframes slideInLeft { from { transform: translateX(-100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } @keyframes slideOutRight { from { transform: translateX(0); opacity: 1; } to { transform: translateX(100%); opacity: 0; } }
2. 在根布局中添加路由过渡逻辑
修改app/layout.tsx,通过usePathname监听路由变化,配合Transition组件(需安装react-transition-group)添加动画类:
import { Transition } from 'react-transition-group'; import { usePathname } from 'next/navigation'; import './globals.css'; import { useEffect, useRef } from 'react'; export default function RootLayout({ children }: { children: React.ReactNode }) { const pathname = usePathname(); const prevPathname = useRef(pathname); // 记录上一次路由路径 useEffect(() => { prevPathname.current = pathname; }, [pathname]); // 判断路由跳转方向 const isToEmail = prevPathname.current === '/registrations/new' && pathname === '/registrations/email'; const isToNew = prevPathname.current === '/registrations/email' && pathname === '/registrations/new'; return ( <html lang="zh-CN"> <body style={{ margin: 0, overflow: 'hidden' }}> <Transition in={true} timeout={300} mountOnEnter unmountOnExit > {(state) => { let animationClass = ''; if (state === 'entering') { animationClass = isToEmail ? 'slide-in-right' : isToNew ? 'slide-in-left' : ''; } else if (state === 'exiting') { animationClass = isToEmail ? 'slide-out-left' : isToNew ? 'slide-out-right' : ''; } return <div className={`page-container ${animationClass}`}>{children}</div>; }} </Transition> </body> </html> ); }
3. 创建路由页面
分别创建app/registrations/new/page.tsx和app/registrations/email/page.tsx,用Link组件实现跳转:
// app/registrations/new/page.tsx import Link from 'next/link'; export default function NewRegistrationPage() { return ( <div style={{ padding: '2rem' }}> <h1>欢迎注册</h1> <Link href="/registrations/email" style={{ fontSize: '1.2rem' }}>使用邮箱注册</Link> </div> ); }
// app/registrations/email/page.tsx import Link from 'next/link'; export default function EmailRegistrationPage() { return ( <div style={{ padding: '2rem' }}> <h1>邮箱注册</h1> <form> <input type="email" placeholder="输入邮箱" style={{ padding: '0.5rem', fontSize: '1rem' }} /> <button type="submit" style={{ marginLeft: '1rem', padding: '0.5rem 1rem' }}>下一步</button> </form> <Link href="/registrations/new" style={{ display: 'block', marginTop: '1rem' }}>返回</Link> </div> ); }
二、Pages Router(Next.js 12及以下)实现方式
1. 定义全局动画样式
在styles/globals.css中添加和App Router一致的动画规则(同上)。
2. 在_app.tsx中监听路由变化
修改pages/_app.tsx,通过useRouter的事件监听来切换动画类:
import { useRouter } from 'next/router'; import { useEffect, useState } from 'react'; import '../styles/globals.css'; function MyApp({ Component, pageProps }) { const router = useRouter(); const [animationClass, setAnimationClass] = useState(''); useEffect(() => { // 路由开始跳转时添加离开动画 const handleRouteStart = (url) => { const targetPath = url.split(window.location.origin)[1]; if (router.pathname === '/registrations/new' && targetPath === '/registrations/email') { setAnimationClass('slide-out-left'); } else if (router.pathname === '/registrations/email' && targetPath === '/registrations/new') { setAnimationClass('slide-out-right'); } }; // 路由跳转完成后添加进入动画 const handleRouteComplete = () => { if (router.pathname === '/registrations/email') { setAnimationClass('slide-in-right'); } else if (router.pathname === '/registrations/new') { setAnimationClass('slide-in-left'); } // 动画结束后清除类名 setTimeout(() => setAnimationClass(''), 300); }; router.events.on('routeChangeStart', handleRouteStart); router.events.on('routeChangeComplete', handleRouteComplete); return () => { router.events.off('routeChangeStart', handleRouteStart); router.events.off('routeChangeComplete', handleRouteComplete); }; }, [router]); return ( <div className={`page-container ${animationClass}`}> <Component {...pageProps} /> </div> ); } export default MyApp;
3. 创建路由页面
在pages/registrations/目录下创建new.tsx和email.tsx,用Link组件实现跳转(代码和App Router的页面类似)。
优化提示
- 可以调整动画时长(比如0.3s)和缓动函数(比如
ease-out)来匹配目标效果; - 如果需要更复杂的转场逻辑,可使用
framer-motion库替代原生CSS动画; - 确保页面容器的
overflow: hidden,避免动画过程中出现滚动条。
内容的提问来源于stack exchange,提问作者Yes
相关产品推荐
相关产品推荐

