使用菜单后Framer Motion页面无法加载的问题求助
React + Framer Motion 页面过渡动画异常问题
我在React项目中使用framer-motion实现页面间过渡动画时遇到异常:在主页滚动一段距离后,点击菜单跳转至其他页面,新页面显示为空。通过开发者工具检查发现,之前的主页仍保留在DOM中(新页面已加载),且主页未执行退出动画。
相关代码
Home组件
import React, {useState, useEffect, useContext} from 'react' import FirstSection from './FirstSection/FirstSection'; import StickyNav from './FirstSection/StickyNav/StickyNav'; import SndSection from "../sections/Content2/SndSection.js" import './Home.css'; import LeftOne from "../sections/FirstSection/LeftOne.js"; import ThirdSection from './Content3/ThirdSection'; import { slideContext } from '../../App'; import { motion } from 'framer-motion'; export default function Home() { const context = useContext(slideContext); const [scroll, setScroll] = useState(); useEffect(() => { const handleScroll = event => { setScroll(window.scrollY); }; window.addEventListener('scroll', handleScroll); return () => { window.removeEventListener('scroll', handleScroll); }; }, []); return ( <motion.div id={context.end ? "end-transition" : ""} className='home' initial={{width:0, opacity:0}} animate={{width:'100%', opacity:1}} exit={{x:window.innerWidth, transition:{duration: 0.3}, opacity:0}} > {scroll >= 100 ? <StickyNav /> : ""} <div id="page"> <LeftOne element='v1'/> <div id="ctn"> <FirstSection /> <SndSection /> <ThirdSection /> </div> </div> </motion.div> ) }
App.js
return ( <HashRouter> <slideContext.Provider value={{end, setEnd, endInscr, setEndInscr, number, setNumber}}> <div className="App"> <AnimatedRoutes /> </div> </slideContext.Provider> </HashRouter> );
AnimatedRoutes组件
import React from 'react' import {Routes, Route, useLocation} from "react-router-dom"; import AboutUs from './AboutUs/AboutUs'; import Error from './Error'; import Inscription from './Inscription/Inscription'; import Thanks from './Inscription/Thanks'; import Privacy from './Privacy/Privacy'; import Home from './sections/Home'; import Terms from './Terms&Conditions/Terms'; import {AnimatePresence} from 'framer-motion'; export default function AnimatedRoutes() { const location = useLocation(); return ( <AnimatePresence> <Routes location={location} key={location.pathname}> <Route path='/' element={<Home />} /> <Route path='/register' element = {<Inscription />} /> <Route path='/thanks-registration' element ={<Thanks />} /> <Route path='/aboutUs' element={<AboutUs />}/> <Route path='/termsAndConditions' element={<Terms />} /> <Route path="/privacy" element={<Privacy />}/> <Route path="/error" element={<Error />} /> </Routes> </AnimatePresence> ) }
问题原因及修复方案
1. 核心问题分析
- 页面滚动后,旧页面的滚动位置未重置,导致framer-motion的退出动画被阻塞,旧组件无法被正确移除
AnimatePresence未设置mode="wait",导致新页面提前渲染,旧页面还未完成退出动画就被叠加,视觉上显示为空- Home组件的scroll状态初始化未赋值,且空字符串渲染可能产生无效节点,干扰动画判断
2. 具体修复代码
修改AnimatedRoutes组件
import React from 'react' import {Routes, Route, useLocation} from "react-router-dom"; import AboutUs from './AboutUs/AboutUs'; import Error from './Error'; import Inscription from './Inscription/Inscription'; import Thanks from './Inscription/Thanks'; import Privacy from './Privacy/Privacy'; import Home from './sections/Home'; import Terms from './Terms&Conditions/Terms'; import {AnimatePresence} from 'framer-motion'; export default function AnimatedRoutes() { const location = useLocation(); // 路由切换时重置滚动位置到顶部 React.useEffect(() => { window.scrollTo(0, 0); }, [location.pathname]); return ( // 设置mode="wait",等待退出动画完成后再渲染新组件 <AnimatePresence mode="wait"> <Routes location={location} key={location.pathname}> <Route path='/' element={<Home />} /> <Route path='/register' element = {<Inscription />} /> <Route path='/thanks-registration' element ={<Thanks />} /> <Route path='/aboutUs' element={<AboutUs />}/> <Route path='/termsAndConditions' element={<Terms />} /> <Route path="/privacy" element={<Privacy />}/> <Route path="/error" element={<Error />} /> </Routes> </AnimatePresence> ) }
优化Home组件
import React, {useState, useEffect, useContext} from 'react' import FirstSection from './FirstSection/FirstSection'; import StickyNav from './FirstSection/StickyNav/StickyNav'; import SndSection from "../sections/Content2/SndSection.js" import './Home.css'; import LeftOne from "../sections/FirstSection/LeftOne.js"; import ThirdSection from './Content3/ThirdSection'; import { slideContext } from '../../App'; import { motion } from 'framer-motion'; export default function Home() { const context = useContext(slideContext); // 初始化scroll为0,避免undefined判断问题 const [scroll, setScroll] = useState(0); useEffect(() => { const handleScroll = event => { setScroll(window.scrollY); }; window.addEventListener('scroll', handleScroll); return () => { window.removeEventListener('scroll', handleScroll); }; }, []); return ( <motion.div id={context.end ? "end-transition" : ""} className='home' initial={{width:0, opacity:0}} animate={{width:'100%', opacity:1}} exit={{x: window.innerWidth, opacity:0, transition:{duration: 0.3}}} // 添加绝对定位确保动画过程中不干扰布局 style={{position: "absolute", top: 0, left: 0, width: "100%"}} > // 用null代替空字符串,避免渲染无效文本节点 {scroll >= 100 ? <StickyNav /> : null} <div id="page"> <LeftOne element='v1'/> <div id="ctn"> <FirstSection /> <SndSection /> <ThirdSection /> </div> </div> </motion.div> ) }
3. 额外检查点
- 确保所有路由页面组件都用
motion.div包裹并设置exit动画,否则AnimatePresence无法处理过渡 - 检查
slideContext中的end状态是否在导航跳转时被正确触发,比如在菜单点击事件中调用setEnd(true)
内容的提问来源于stack exchange,提问作者Adam k
相关产品推荐
相关产品推荐

