如何为React Router v4(搭配react-router-config)配置JS非CSS动画(含GSAP)
嘿,刚好我之前折腾过React Router v4 + react-router-config + GSAP的JS动画方案,踩过几个小坑,给你梳理下具体的实现步骤,应该能帮到你:
1. 先搭好react-router-config的基础路由框架
首先得确保你的路由配置是基于react-router-config的数组格式,这是后续做动画的基础:
// routes.js import Home from './pages/Home'; import About from './pages/About'; import Contact from './pages/Contact'; export default [ { path: '/', exact: true, component: Home, // 可以给需要特殊动画的路由加meta字段 meta: { animation: 'fade' } }, { path: '/about', component: About, meta: { animation: 'slideLeft' } }, { path: '/contact', component: Contact, meta: { animation: 'slideRight' } } ];
然后在根组件里用RenderRoutes渲染路由:
// App.js import { RenderRoutes } from 'react-router-config'; import routes from './routes'; function App() { return <RenderRoutes routes={routes} />; }
2. 封装全局过渡组件,结合GSAP做动画
核心思路是监听路由location的变化,在路由切换前后触发GSAP动画,同时控制动画期间的路由拦截(避免用户快速点跳转搞乱动画)。
这里用函数组件+hooks来实现,类组件逻辑类似:
// components/PageTransition.js import React, { useState, useEffect, useRef } from 'react'; import { useLocation, useNavigate } from 'react-router-dom'; import { gsap } from 'gsap'; const PageTransition = ({ children }) => { const location = useLocation(); const navigate = useNavigate(); const [isAnimating, setIsAnimating] = useState(false); const contentRef = useRef(null); const prevLocation = useRef(location); // 监听路由变化,触发动画 useEffect(() => { if (location.pathname !== prevLocation.current.pathname) { setIsAnimating(true); // 先执行当前页面的退出动画 const exitAnim = gsap.timeline({ onComplete: () => { // 退出动画完成后,更新路由记录,执行新页面的进入动画 prevLocation.current = location; const enterAnim = gsap.timeline(); // 根据路由meta选择对应的进入动画 const animationType = location.state?.animation || findRouteMeta(location.pathname)?.animation || 'fade'; switch(animationType) { case 'slideLeft': enterAnim.fromTo(contentRef.current, { x: '100%', opacity: 0 }, { x: 0, opacity: 1, duration: 0.6 }); break; case 'slideRight': enterAnim.fromTo(contentRef.current, { x: '-100%', opacity: 0 }, { x: 0, opacity: 1, duration: 0.6 }); break; default: enterAnim.fromTo(contentRef.current, { opacity: 0 }, { opacity: 1, duration: 0.4 }); } enterAnim.eventCallback('onComplete', () => setIsAnimating(false)); } }); // 退出动画逻辑(同样可以根据meta定制) exitAnim.to(contentRef.current, { opacity: 0, duration: 0.3 }); } }, [location]); // 动画期间拦截路由跳转,避免冲突 useEffect(() => { const unblock = navigate.block((tx) => { if (isAnimating) { alert('动画正在进行中,请稍后再操作'); // 也可以静默阻止,这里给用户提示 return false; } unblock(); tx.retry(); }); return unblock; }, [isAnimating, navigate]); // 辅助函数:根据路径找到对应的路由meta const findRouteMeta = (path) => { const matchedRoute = routes.find(route => route.path === path); return matchedRoute?.meta; }; return ( <div ref={contentRef} style={{ position: 'fixed', top: 0, left: 0, width: '100%', minHeight: '100vh', overflow: 'hidden' }} > {/* 动画期间不渲染新内容,避免DOM混乱 */} {!isAnimating && children} </div> ); }; export default PageTransition;
3. 把过渡组件整合到路由渲染中
修改根组件,用PageTransition包裹RenderRoutes:
// App.js import { RenderRoutes } from 'react-router-config'; import PageTransition from './components/PageTransition'; import routes from './routes'; function App() { return ( <PageTransition> <RenderRoutes routes={routes} /> </PageTransition> ); }
4. 特殊场景的处理技巧
- 嵌套路由动画:如果有嵌套路由,把过渡组件放在需要动画的层级,比如只给一级路由加过渡,嵌套路由内部的动画单独处理。
- 动态路由参数变化:比如
/user/:id切换时,location.pathname会变化,如果你不想触发页面级动画,可以监听location.search或location.state来区分。 - GSAP实例清理:如果用了ScrollTrigger等高级插件,记得在路由切换时销毁实例,避免内存泄漏:
import { ScrollTrigger } from 'gsap/ScrollTrigger'; // 在退出动画开始前清理 ScrollTrigger.getAll().forEach(trigger => trigger.kill());
内容的提问来源于stack exchange,提问作者Bram z
相关产品推荐
相关产品推荐

