react-router-dom搭配react-spring时浏览器返回恢复滚动位置方案
解决方案
react-spring的转场动画会同时保留入场、出场两个页面的DOM节点直到动画完全结束,浏览器默认的scrollRestoration或者大部分路由自带的滚动恢复逻辑,都是在路由切换瞬间触发,这时候要么入场页面还没完成渲染、要么动画过程中DOM布局还不稳定,才会导致滚动恢复失效,可按以下步骤实现需求:
1. 禁用默认滚动恢复
首先关掉浏览器自带的滚动恢复,避免逻辑冲突,在项目入口文件添加:
if ('scrollRestoration' in window.history) { window.history.scrollRestoration = 'manual' }
2. 页面退场前存储滚动位置
react-spring会等退场动画结束才卸载组件,这时候滚动位置是准确的,在每个页面组件的卸载生命周期将当前滚动位置存入当前history记录的state中,可避免多标签页数据冲突:
import { useHistory } from 'react-router-dom' function YourPageComponent() { const history = useHistory() useEffect(() => { // 组件卸载前保存滚动位置 return () => { history.replace({ ...history.location, state: { ...history.location.state, savedScrollY: window.scrollY } }) } }, [history]) // 页面其他逻辑 }
如果是容器内滚动,替换window.scrollY为对应容器DOM的scrollTop即可。
3. 入场动画结束后恢复滚动
在react-spring转场的入场动画完成回调onRest中执行滚动恢复,确保此时入场页面已经完全挂载、布局稳定:
import { useTransition, animated } from '@react-spring/web' import { useLocation, useHistory, Routes } from 'react-router-dom' function App() { const location = useLocation() const history = useHistory() const transitions = useTransition(location, { from: { opacity: 0, x: '100%' }, enter: { opacity: 1, x: '0%' }, leave: { opacity: 0, x: '-100%' }, }) return transitions((style, loc) => ( <animated.div style={{ ...style, position: 'absolute', width: '100%' }} // 动画完全结束后触发恢复逻辑 onRest={() => { const savedScrollY = loc.state?.savedScrollY if (typeof savedScrollY === 'number') { window.scrollTo(0, savedScrollY) // 恢复完成后清除存储的滚动位置,避免重复触发 history.replace({ ...loc, state: { ...loc.state, savedScrollY: undefined } }) } }} > <Routes location={loc}> {/* 你的路由配置 */} </Routes> </animated.div> )) }
额外适配场景
- 如果页面有异步渲染的内容(比如接口返回的列表),可以把
savedScrollY暂存在状态里,等异步内容渲染完成后再执行一次scrollTo即可。 - 如果使用虚拟滚动组件,直接将存储的滚动位置传给虚拟滚动组件的对应属性即可,不需要操作全局滚动。
内容的提问来源于stack exchange,提问作者ale917k
相关产品推荐
相关产品推荐

