如何修复React Router v4页面过渡动画重复显示Bug?
我之前也碰到过这个一模一样的问题!核心原因是 TransitionGroup 在路由切换时会同时挂载新旧两个路由组件,直到旧组件的退出动画完成,这就导致了视觉上的重叠。用 fixed 确实能快速解决重叠,但代价是破坏了正常的文档流,动态高度的组件自然会把 Footer 挤乱。下面给你几个靠谱的解决思路:
方案一:优化 react-transition-group 的布局(不破坏文档流)
这个思路的核心是用相对定位的容器包裹路由组件,配合 CSS 控制过渡时的组件定位,既解决重叠问题,又保留正常的文档流让 Footer 位置正确。
组件结构代码
import { TransitionGroup, CSSTransition } from 'react-transition-group'; import { Switch, Route, withRouter } from 'react-router-dom'; // 用withRouter包裹获取location对象 const App = withRouter(({ location }) => ( <div className="app-container"> {/* 路由过渡容器,负责占满除Footer外的空间 */} <div className="route-transition-wrapper"> <TransitionGroup> {/* 必须给CSSTransition设置唯一key,这里用location.key保证每次路由切换都触发过渡 */} <CSSTransition key={location.key} timeout={500} // 动画时长,要和CSS里的transition时长一致 classNames="fade" unmountOnExit // 旧组件退出动画完成后自动卸载,避免冗余DOM > {/* 必须把location传给Switch,否则TransitionGroup无法正确识别路由变化 */} <Switch location={location}> <Route path="/" exact component={Home} /> <Route path="/about" component={About} /> <Route path="/contact" component={Contact} /> </Switch> </CSSTransition> </TransitionGroup> </div> {/* Footer放在容器外面,跟着文档流自动定位到底部 */} <footer className="app-footer">我是不会乱跑的Footer</footer> </div> )); export default App;
对应的CSS样式
/* 让整个应用容器占满视口,用flex布局控制Footer位置 */ .app-container { min-height: 100vh; display: flex; flex-direction: column; } /* 路由过渡容器占满除Footer外的所有空间 */ .route-transition-wrapper { flex: 1; position: relative; /* 关键:让内部绝对定位的组件相对于这个容器 */ overflow: hidden; /* 防止过渡时组件内容溢出容器 */ } /* 淡入淡出动画的CSS类,和CSSTransition的classNames对应 */ .fade-enter { opacity: 0; position: absolute; /* 新组件绝对定位在容器内,覆盖旧组件 */ top: 0; left: 0; right: 0; } .fade-enter-active { opacity: 1; transition: opacity 500ms ease-in-out; } .fade-exit { opacity: 1; } .fade-exit-active { opacity: 0; transition: opacity 500ms ease-in-out; }
解释一下:这里用flex布局让app-container占满整个视口,route-transition-wrapper通过flex:1自动填充除Footer外的空间。给容器设置position:relative后,新进入的路由组件用position:absolute只会相对于这个容器定位,不会脱离整个文档流,Footer自然能留在底部,不会被覆盖。unmountOnExit属性则会在旧组件的退出动画完成后立即卸载它,避免页面上存在冗余的DOM节点。
方案二:用 framer-motion 简化路由过渡
如果你觉得手动写CSS和布局太麻烦,可以试试framer-motion这个动画库,它处理路由过渡的逻辑更简洁,还能自动处理组件的挂载时机,完全不用操心重叠问题。
组件结构代码
import { AnimatePresence } from 'framer-motion'; import { Switch, Route, useLocation } from 'react-router-dom'; function App() { const location = useLocation(); return ( <div className="app-container"> {/* AnimatePresence负责管理组件的进入/退出动画 */} <AnimatePresence mode="wait"> {/* 给Switch设置key,确保路由切换时触发动画 */} <Switch location={location} key={location.pathname}> <Route path="/" exact> <Home initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.5 }} /> </Route> <Route path="/about"> <About initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.5 }} /> </Route> </Switch> </AnimatePresence> <footer className="app-footer">我是不会乱跑的Footer</footer> </div> ); } export default App;
基础CSS(只需要处理Footer布局)
.app-container { min-height: 100vh; display: flex; flex-direction: column; } /* 路由组件容器占满剩余空间 */ .route-content { flex: 1; } .app-footer { padding: 1rem; background: #f5f5f5; }
解释:AnimatePresence的mode="wait"属性会等待旧组件的退出动画完全完成后,再挂载新组件,从根源上避免了新旧组件的重叠问题。而且所有组件都保持正常的文档流布局,Footer的位置完全不用额外调整,非常省心。
最后总结
如果坚持用react-transition-group,方案一完全可以解决你的问题,既保留淡入淡出效果,又不会让Footer错乱;如果想简化开发流程,framer-motion是更好的选择,它的动画能力更强,代码也更简洁。
内容的提问来源于stack exchange,提问作者user10013590

