You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,或需要定制逻辑,可以优化手动实现方式:

  1. 实时记录滚动位置
    不要只在点击<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>
      );
    }
    
  2. 回到页面时再恢复滚动
    把恢复逻辑放在第一个页面的useEffect中,并用requestAnimationFrame确保页面渲染完成后执行:

    useEffect(() => {
      const scrollPos = sessionStorage.getItem('homeScroll');
      if (scrollPos) {
        requestAnimationFrame(() => {
          window.scrollTo(0, parseInt(scrollPos));
        });
      }
    }, []);
    
  3. 可选:清理存储值
    如果需要直接访问第一个页面时回到顶部,可以在组件卸载时清理存储:

    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 13:30:59