React.js中从其他页面返回时如何保留初始页面的滚动位置?
如何在React Router中保存并恢复页面滚动位置
问题场景
我有两个路由页面:
第一个页面包含<Link>组件:
<Link to="/something"> <Button> This is Link </Button> </Link>
第二个页面有返回按钮:
const handleGoBack = () => { navigate(-1); }; return ( <Button onClick={handleGoBack}> Back </Button> );
我需要实现:点击第一个页面的<Link>时保存当前滚动位置,从第二个页面返回时,能回到点击链接时的精确位置。
我试过在第一个组件中添加滚动存储函数,绑定到<Link>的onClick事件:
function storeScrollPosition() { sessionStorage.setItem('scrollPosition', window.scrollY.toString()); }
<Link to="/something" onClick={storeScrollPosition}> <Button> This is Link </Button> </Link>
同时在第二个组件的useEffect中处理恢复逻辑:
useEffect(() => { const scrollPosition = sessionStorage.getItem('scrollPosition'); if (scrollPosition) { window.scrollTo(0, parseInt(scrollPosition)); sessionStorage.removeItem('scrollPosition'); } else { window.scrollTo(0, 0); } }, []);
但这个方案不稳定,有时正常有时失效,推测和页面渲染时机有关。有没有更可靠的实现方式?
*使用的是react-router-dom进行导航。
解决方案
方案一:用React Router v6.4+内置滚动恢复(推荐)
React Router v6.4及以上版本的createBrowserRouter已经内置了滚动恢复功能,默认会记录每个路由的滚动位置,返回时自动恢复,无需手动处理:
import { createBrowserRouter, RouterProvider } from 'react-router-dom'; const router = createBrowserRouter([ { path: '/', element: <HomePage />, }, { path: '/something', element: <SomethingPage />, }, ]); function App() { return <RouterProvider router={router} />; }
方案二:手动优化滚动恢复逻辑(兼容旧版本)
如果使用低版本React Router,或需要定制逻辑,可以优化手动实现方式:
实时记录滚动位置
不要只在点击<Link>时存储,而是实时监听滚动事件更新存储值,避免点击时机问题:import { useEffect } from 'react'; import { Link } from 'react-router-dom'; function HomePage() { useEffect(() => { const handleScroll = () => { sessionStorage.setItem('homeScroll', window.scrollY.toString()); }; window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, []); return ( <Link to="/something"> <Button>This is Link</Button> </Link> ); }回到页面时再恢复滚动
把恢复逻辑放在第一个页面的useEffect中,并用requestAnimationFrame确保页面渲染完成后执行:useEffect(() => { const scrollPos = sessionStorage.getItem('homeScroll'); if (scrollPos) { requestAnimationFrame(() => { window.scrollTo(0, parseInt(scrollPos)); }); } }, []);可选:清理存储值
如果需要直接访问第一个页面时回到顶部,可以在组件卸载时清理存储:useEffect(() => { return () => sessionStorage.removeItem('homeScroll'); }, []);
方案三:全局路由监听统一处理
用useLocation监听路由变化,统一管理所有页面的滚动位置:
import { useEffect, useRef } from 'react'; import { useLocation } from 'react-router-dom'; function ScrollRestoration() { const location = useLocation(); const scrollPositions = useRef({}); // 离开页面时存储当前滚动位置 useEffect(() => { scrollPositions.current[location.pathname] = window.scrollY; }, [location.pathname]); // 进入页面时恢复对应位置 useEffect(() => { const pos = scrollPositions.current[location.pathname]; if (pos) { requestAnimationFrame(() => window.scrollTo(0, pos)); } else { window.scrollTo(0, 0); } }, [location.pathname]); return null; }
将这个组件放在路由根组件中:
function App() { return ( <BrowserRouter> <ScrollRestoration /> <Routes> <Route path="/" element={<HomePage />} /> <Route path="/something" element={<SomethingPage />} /> </Routes> </BrowserRouter> ); }
原方案失效原因
- 点击
<Link>的onClick事件可能在跳转前执行,但此时滚动位置可能因页面动画等因素不准确 - 错误地在第二个页面处理滚动恢复,应该回到目标页面(第一个页面)时再执行恢复逻辑
- 直接调用
window.scrollTo可能在页面未渲染完成时执行,导致滚动不生效,需要用requestAnimationFrame确保DOM就绪后执行
内容的提问来源于stack exchange,提问作者nikolaPantelic98
相关产品推荐
相关产品推荐

