React Router v6打开模态框时背景页回到顶部如何解决
问题根因
React Router v6 默认在路由切换时会自动把页面滚动位置重置到顶部。你通过backgroundLocation保留背景路由的实现思路本身没有问题,但点击链接触发跳转时,实际路由已经切换到模态框对应的路径,默认滚动逻辑会先执行滚回顶部的操作;关闭模态框路由回退时,RRD 又会读取之前缓存的背景页滚动位置重新还原,一来一回就出现了明显的跳顶抖动。
核心解决思路:模态框触发的路由跳转场景下,拦截默认的滚动重置逻辑即可。
适配你现有代码的修复方案
你的代码用的是普通Routes组件的非数据路由写法,不需要重构路由结构,按以下步骤修改即可:
- 修正路由组件命名冲突问题:你自定义的路由组件和react-router导出的
Routes组件同名,会导致标识符报错,先把自定义组件重命名为AppRoutes之类的非冲突名称。 - 新增滚动逻辑拦截,存在背景路由(即模态框打开场景)时禁止自动滚到顶部:
import React, { useEffect } from "react"; import { Route, Routes, useLocation } from "react-router-dom"; import Home from "./pages/Home"; import Page from "./pages/Page"; import PageVersion from "./pages/PageVersion"; import ModalVersion from "./components/ModalVersion"; const AppRoutes: React.FC = () => { const location = useLocation(); const state = location.state as { backgroundLocation?: Location; }; // 拦截默认滚动重置逻辑 useEffect(() => { // 模态框打开场景直接返回,不执行滚动重置 if (state?.backgroundLocation) return; // 普通页面跳转才执行滚动到顶部 window.scrollTo(0, 0); }, [location.pathname, state?.backgroundLocation]); return ( <> {/* 背景路由层 */} <Routes location={state?.backgroundLocation || location}> <Route path={'/'} element={<Home />} /> <Route path={'/page'} element={<Page />} /> <Route path={'/page/:more_info'} element={<PageVersion />} /> </Routes> {/* 模态框路由层 */} {state?.backgroundLocation && ( <Routes> <Route path={'/page/:more_info'} element={<ModalVersion />} /> </Routes> )} </> ) } export default AppRoutes;
- 修复Page组件的语法错误:
- 导入语句的
form拼写错误改为from - 补充
useLocation调用获取当前路由实例,避免传入state的backgroundLocation为undefined
- 导入语句的
import { Link } from "@mui/material" import { Link as RouterLink, useLocation } from "react-router-dom"; import APageComponentContainingALink from "./components/APageComponentContainingALink"; const Page: React.FC = () => { const location = useLocation(); return ( <APageComponentContainingALink> <Link component={RouterLink} to="/page/some_info" state={{ backgroundLocation: location }} > 打开详情 </Link> </APageComponentContainingALink> ) } export { Page };
- 确认模态框滚动锁定配置:
如果你用的是MUI等UI库自带的Modal组件,组件默认内置了背景页滚动锁定逻辑,不需要手动写body样式修改代码,只要不要手动开启
disableScrollLock属性即可。如果是自定义Modal组件,在模态框挂载时固定body位置记录滚动偏移,卸载时还原即可,示例代码:
import { useEffect } from "react"; import Modal from "./components/Modal"; import Info from "./components/Info"; const ModalVersion: React.FC = () => { useEffect(() => { const scrollY = window.scrollY; // 固定背景页 document.body.style.position = 'fixed'; document.body.style.top = `-${scrollY}px`; document.body.style.width = '100%'; document.body.style.overflow = 'hidden'; return () => { // 关闭时还原 document.body.style.position = ''; document.body.style.top = ''; document.body.style.width = ''; document.body.style.overflow = ''; window.scrollTo(0, scrollY); } }, []) return ( <Modal> <Info /> </Modal> ) } export { ModalVersion };
v6.4+ 数据路由适配方案
如果你后续升级到用createBrowserRouter+RouterProvider的数据路由写法,可以直接用官方提供的滚动恢复hook配置拦截规则,不需要手动写scrollTo逻辑:
import { useScrollRestoration, useLocation } from "react-router-dom"; function ScrollControl() { const location = useLocation(); const state = location.state as { backgroundLocation?: Location }; useScrollRestoration({ preventScrollReset: () => !!state?.backgroundLocation }); return null; }
把该组件放在路由根节点下即可生效。
内容的提问来源于stack exchange,提问作者gosshawk
相关产品推荐
相关产品推荐

