React Router + Framer Motion:如何动态设置路由动画的初始/退出值?
解决方案
方案一:通过Context存储导航方向,动态控制动画参数
核心是在全局Context中记录用户的滚动导航方向(向下/向上),让每个路由组件根据方向动态计算动画的initial和exit值。
- 更新Context.js,添加导航方向状态:
import React, { useState, useEffect } from "react"; import { useNavigate, useLocation } from "react-router-dom"; const Context = React.createContext(); function ContextProvider({ children }) { const navigate = useNavigate(); const location = useLocation(); const [navDirection, setNavDirection] = useState(null); // 存储'down'或'up' useEffect(() => { window.addEventListener("wheel", handleNavigation); return () => window.removeEventListener("wheel", handleNavigation); }, [location]); function handleNavigation(e) { if (location.pathname.includes("/work/")) return; const isScrollDown = e.deltaY > 1; setNavDirection(isScrollDown ? 'down' : 'up'); if (isScrollDown && location.pathname === "/") { navigate("/skills"); } else if (!isScrollDown && location.pathname === "/skills") { navigate(""); } else if (isScrollDown && location.pathname === "/skills") { navigate("/aboutme"); } else if (!isScrollDown && location.pathname === "/aboutme") { navigate("/skills"); } } return <Context.Provider value={{ navDirection }}>{children}</Context.Provider>; } export { ContextProvider, Context };
- 修改Skills组件(Main和AboutMe同理适配):
import React, { useContext } from "react"; import { motion } from "framer-motion"; import { Context } from "./Context"; import { useLocation } from "react-router-dom"; export default function Skills() { const { navDirection } = useContext(Context); const location = useLocation(); // 进入动画:从首页向下滚动来的,初始y=1000;从关于页向上滚动来的,初始y=-1000 const initialY = navDirection === 'down' ? 1000 : -1000; // 退出动画:回退到首页(向上滚动)时y=1000;跳转到关于页(向下滚动)时y=-1000 const exitY = location.pathname === '/skills' && navDirection === 'up' ? 1000 : -1000; return ( <motion.div className="skills--container" initial={{ opacity: 0, y: initialY, transition: { duration: 0.8 } }} animate={{ opacity: 1, y: 0, transition: { duration: 0.8 } }} exit={{ opacity: 0, y: exitY, transition: { duration: 0.8 } }} > <p>Skills Page</p> </motion.div> ); }
方案二:利用React Router的state传递导航信息
在调用navigate时携带路由跳转的方向状态,目标路由组件通过useLocation获取该状态,动态设置动画参数。
- 更新Context.js中的handleNavigation方法:
function handleNavigation(e) { if (location.pathname.includes("/work/")) return; const isScrollDown = e.deltaY > 1; if (isScrollDown && location.pathname === "/") { navigate("/skills", { state: { from: 'down' } }); } else if (!isScrollDown && location.pathname === "/skills") { navigate("", { state: { from: 'up' } }); } else if (isScrollDown && location.pathname === "/skills") { navigate("/aboutme", { state: { from: 'down' } }); } else if (!isScrollDown && location.pathname === "/aboutme") { navigate("/skills", { state: { from: 'up' } }); } }
- 修改Skills组件:
import React from "react"; import { motion } from "framer-motion"; import { useLocation } from "react-router-dom"; export default function Skills() { const location = useLocation(); const fromDirection = location.state?.from; // 动态设置进入动画的y值 const initialY = fromDirection === 'down' ? 1000 : -1000; return ( <motion.div className="skills--container" initial={{ opacity: 0, y: initialY, transition: { duration: 0.8 } }} animate={{ opacity: 1, y: 0, transition: { duration: 0.8 } }} exit={({ target }) => { // 根据退出目标路由判断y值:回首页则y=1000,去关于页则y=-1000 const isGoingToHome = target.pathname === '/'; return { opacity: 0, y: isGoingToHome ? 1000 : -1000, transition: { duration: 0.8 } }; }} > <p>Skills Page</p> </motion.div> ); }
更优方案:改用单页滚动实现(无需路由)
如果你的页面是线性的整页滚动结构,完全可以抛弃React Router,直接用Framer Motion实现单页滚动,动画控制会更简洁:
import React, { useState, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import Main from "./Main"; import Skills from "./Skills"; import AboutMe from "./AboutMe"; const pageList = [ { id: 'home', component: <Main /> }, { id: 'skills', component: <Skills /> }, { id: 'aboutme', component: <AboutMe /> } ]; export default function FullPageScroll() { const [currentIndex, setCurrentIndex] = useState(0); const [prevIndex, setPrevIndex] = useState(0); useEffect(() => { const handleWheel = (e) => { const isScrollDown = e.deltaY > 1; if (isScrollDown && currentIndex < pageList.length - 1) { setPrevIndex(currentIndex); setCurrentIndex(prev => prev + 1); } else if (!isScrollDown && currentIndex > 0) { setPrevIndex(currentIndex); setCurrentIndex(prev => prev - 1); } }; window.addEventListener('wheel', handleWheel); return () => window.removeEventListener('wheel', handleWheel); }, [currentIndex]); return ( <div style={{ height: '100vh', overflow: 'hidden' }}> <AnimatePresence mode="wait"> <motion.div key={pageList[currentIndex].id} initial={{ y: currentIndex > prevIndex ? 1000 : -1000 }} animate={{ y: 0 }} exit={{ y: currentIndex < prevIndex ? 1000 : -1000 }} transition={{ duration: 0.8 }} style={{ height: '100vh', position: 'absolute', width: '100%' }} > {pageList[currentIndex].component} </motion.div> </AnimatePresence> </div> ); }
内容的提问来源于stack exchange,提问作者jacksonmendoza
相关产品推荐
相关产品推荐

