NextJS App Router+GSAP作品集:预加载器与页面过渡实现咨询
解决方案:Next.js App Router + GSAP 预加载器与页面过渡实现
一、优质预加载器实现
核心思路
抛弃固定延迟,结合页面实际加载状态(路由切换pending状态、数据/资源加载完成信号)+ GSAP动态动画,实现流畅的加载体验。
代码实现示例
在template.tsx中:
'use client'; import { useState, useEffect } from 'react'; import { useRouter } from 'next/navigation'; import gsap from 'gsap'; export default function Template({ children }: { children: React.ReactNode }) { const [showPreloader, setShowPreloader] = useState(true); const router = useRouter(); useEffect(() => { // 初始化预加载器入场动画 const preloaderTl = gsap.timeline(); preloaderTl.from('.preloader__progress', { scaleX: 0, duration: 1, ease: 'power2.out' }); // 监听路由切换前后状态 const handleRouteChange = () => { setShowPreloader(true); // 重置进度条动画 gsap.to('.preloader__progress', { scaleX: 0, duration: 0 }); gsap.from('.preloader__progress', { scaleX: 0, duration: 0.8, ease: 'power2.out' }); }; const handleRouteComplete = () => { // 预加载器退场动画 gsap.to('.preloader', { opacity: 0, y: -100, duration: 0.5, ease: 'power2.in', onComplete: () => setShowPreloader(false) }); }; router.beforeNavigate(handleRouteChange); router.afterNavigate(handleRouteComplete); // 首次加载完成后自动隐藏 window.addEventListener('load', () => { setTimeout(handleRouteComplete, 500); // 留缓冲时间展示动画 }); return () => { router.beforeNavigate(null); router.afterNavigate(null); window.removeEventListener('load', handleRouteComplete); }; }, [router]); return ( <> {showPreloader && ( <div className="preloader fixed inset-0 bg-white flex items-center justify-center z-50"> <div className="preloader__container w-64 h-2 bg-gray-200 rounded-full overflow-hidden"> <div className="preloader__progress h-full bg-black rounded-full"></div> </div> </div> )} <div className="page-content">{children}</div> </> ); }
优化点
- 可替换进度条为自定义动画(如SVG路径绘制、形状变形),用GSAP的
drawSVG或morphSVG插件增强视觉效果 - 结合
useRouter().isPending状态,更精准控制动画触发时机
二、Barba.js风格容器式页面过渡
核心思路
利用Next.js App Router的template作为过渡容器,通过路由切换前后的内容容器切换,配合GSAP实现旧页面退场、新页面入场的无缝动画,模拟Barba的容器替换逻辑。
代码实现示例
在template.tsx中:
'use client'; import { useState, useEffect, useRef } from 'react'; import { useRouter, usePathname } from 'next/navigation'; import gsap from 'gsap'; export default function TransitionTemplate({ children }: { children: React.ReactNode }) { const [isTransitioning, setIsTransitioning] = useState(false); const [currentContent, setCurrentContent] = useState(children); const router = useRouter(); const pathname = usePathname(); const contentRef = useRef<HTMLDivElement>(null); useEffect(() => { const handleBeforeNavigate = () => { setIsTransitioning(true); // 根据目标路由做差异化退场动画 const exitTl = gsap.timeline(); if (pathname === '/') { exitTl.to(contentRef.current, { opacity: 0, scale: 0.95, duration: 0.4, ease: 'power2.in' }); } else { exitTl.to(contentRef.current, { opacity: 0, x: -50, duration: 0.4, ease: 'power2.in' }); } }; const handleAfterNavigate = () => { // 等待DOM更新后触发新页面入场动画 setTimeout(() => { setCurrentContent(children); const enterTl = gsap.timeline(); if (pathname === '/contact' || pathname === '/about') { enterTl.from(contentRef.current, { opacity: 0, x: 50, duration: 0.4, ease: 'power2.out' }); } else { enterTl.from(contentRef.current, { opacity: 0, scale: 1.05, duration: 0.4, ease: 'power2.out' }); } enterTl.eventCallback('onComplete', () => setIsTransitioning(false)); }, 100); }; router.beforeNavigate(handleBeforeNavigate); router.afterNavigate(handleAfterNavigate); return () => { router.beforeNavigate(null); router.afterNavigate(null); }; }, [router, children, pathname]); return ( <div className="transition-container"> {!isTransitioning && ( <div ref={contentRef} className="page-content">{currentContent}</div> )} {/* 全屏遮罩增强沉浸感 */} {isTransitioning && ( <div className="transition-overlay fixed inset-0 bg-black/80 z-40"></div> )} </div> ); }
进阶优化
- 通过
usePathname()区分不同路由,实现针对性的过渡动画(如首页到联系页用横向滑动,到关于页用缩放) - 用GSAP的
timeline组合多步动画(遮罩入场→内容切换→遮罩退场) - 结合
layout.tsx实现局部内容过渡,而非全页切换
参考方向
- GitHub仓库:搜索
nextjs app router gsap page transitions,参考作品集类项目的预加载器和过渡实现逻辑 - GSAP官方文档:重点学习
timeline、插件(如morphSVG)的使用,以及路由事件结合动画的最佳实践
内容的提问来源于stack exchange,提问作者tunv
相关产品推荐
相关产品推荐

