React-router在每次转换时滚动到顶部

我有一个问题,当导航到另一个页面,它的位置将保持像之前的页面。所以它不会自动滚动到顶部。 我还尝试在onChange路由器上使用window.scrollTo(0, 0)。我还使用了scrollBehavior来修复这个问题,但它没有工作。对此有什么建议吗?< / p >
213272 次浏览

我发现ReactDOM.findDomNode(this).scrollIntoView()正在工作。我把它放在componentDidMount()里面。

这个答案是遗留代码,对于路由器v4+检查其他答案

<Router onUpdate={() => window.scrollTo(0, 0)} history={createBrowserHistory()}>
...
</Router>

如果它不起作用,你应该找到原因。也在componentDidMount

document.body.scrollTop = 0;
// or
window.scrollTo(0,0);

你可以用:

componentDidUpdate() {
window.scrollTo(0,0);
}

你可以添加一些像" scrolling = false"这样的标志,然后在update中:

componentDidUpdate() {
if(this.scrolled === false){
window.scrollTo(0,0);
scrolled = true;
}
}

这有点老套(但很有效):我只是添加

window.scrollTo (0,0);

呈现();

我的申请也遇到了同样的问题。使用下面的代码片段可以帮助我在单击下一个按钮时滚动到页面顶部。

<Router onUpdate={() => window.scrollTo(0, 0)} history= {browserHistory}>
...
</Router>

然而,在浏览器返回时,这个问题仍然存在。经过大量的尝试,我意识到这是因为浏览器窗口的历史对象,它有一个属性scrollRestoration被设置为auto。将此设置为手动解决了我的问题。

function scrollToTop() {
window.scrollTo(0, 0)
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}
}


<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>

对于react-router v4,这里有一个实现滚动恢复的创建-反应应用程序:http://router-scroll-top.surge.sh/

为了实现这一点,你可以创建装饰Route组件并利用生命周期方法:

import React, { Component } from 'react';
import { Route, withRouter } from 'react-router-dom';


class ScrollToTopRoute extends Component {
componentDidUpdate(prevProps) {
if (this.props.path === this.props.location.pathname && this.props.location.pathname !== prevProps.location.pathname) {
window.scrollTo(0, 0)
}
}


render() {
const { component: Component, ...rest } = this.props;


return <Route {...rest} render={props => (<Component {...props} />)} />;
}
}


export default withRouter(ScrollToTopRoute);

componentDidUpdate上,我们可以检查位置路径名何时改变,并将其与path道具匹配,如果满足,则恢复窗口滚动。

这个方法最酷的地方在于,我们可以有恢复滚动的路由和不恢复滚动的路由。

下面是一个App.js的例子,你可以使用上面的方法:

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link } from 'react-router-dom';
import Lorem from 'react-lorem-component';
import ScrollToTopRoute from './ScrollToTopRoute';
import './App.css';


const Home = () => (
<div className="App-page">
<h2>Home</h2>
<Lorem count={12} seed={12} />
</div>
);


const About = () => (
<div className="App-page">
<h2>About</h2>
<Lorem count={30} seed={4} />
</div>
);


const AnotherPage = () => (
<div className="App-page">
<h2>This is just Another Page</h2>
<Lorem count={12} seed={45} />
</div>
);


class App extends Component {
render() {
return (
<Router>
<div className="App">
<div className="App-header">
<ul className="App-nav">
<li><Link to="/">Home</Link></li>
<li><Link to="/about">About</Link></li>
<li><Link to="/another-page">Another Page</Link></li>
</ul>
</div>
<Route exact path="/" component={Home} />
<ScrollToTopRoute path="/about" component={About} />
<ScrollToTopRoute path="/another-page" component={AnotherPage} />
</div>
</Router>
);
}
}


export default App;

从上面的代码中,有趣的是,只有当导航到/about/another-page时,才会执行滚动到顶部的操作。然而,当使用/时,不会发生滚动恢复。

整个代码库可以在这里找到:https://github.com/rizedr/react-router-scroll-top

这个答案只适用于v4版本,而不适用于后续版本。

React Router v4的文档包含了用于卷轴恢复的代码示例。下面是他们的第一个代码示例,它作为一个全站解决方案,当页面导航到“滚动到顶部”:

class ScrollToTop extends Component {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0)
}
}


render() {
return this.props.children
}
}


export default withRouter(ScrollToTop)

然后在你的应用程序的顶部渲染它,但在路由器下面:

const App = () => (
<Router>
<ScrollToTop>
<App/>
</ScrollToTop>
</Router>
)


// or just render it bare anywhere you want, but just one :)
<ScrollToTop/>

^直接从文档中复制

显然,这适用于大多数情况,但是关于如何处理选项卡接口以及为什么没有实现通用解决方案还有更多内容。

我写了一个名为withScrollToTop的高阶组件。这个HOC有两个标志:

  • onComponentWillMount -是否在导航时滚动到顶部(componentWillMount)
  • onComponentDidUpdate -更新时是否滚动到顶部(componentDidUpdate)。该标志在组件未卸载但发生导航事件的情况下是必要的,例如,从/users/1/users/2

// @flow
import type { Location } from 'react-router-dom';
import type { ComponentType } from 'react';


import React, { Component } from 'react';
import { withRouter } from 'react-router-dom';


type Props = {
location: Location,
};


type Options = {
onComponentWillMount?: boolean,
onComponentDidUpdate?: boolean,
};


const defaultOptions: Options = {
onComponentWillMount: true,
onComponentDidUpdate: true,
};


function scrollToTop() {
window.scrollTo(0, 0);
}


const withScrollToTop = (WrappedComponent: ComponentType, options: Options = defaultOptions) => {
return class withScrollToTopComponent extends Component<Props> {
props: Props;


componentWillMount() {
if (options.onComponentWillMount) {
scrollToTop();
}
}


componentDidUpdate(prevProps: Props) {
if (options.onComponentDidUpdate &&
this.props.location.pathname !== prevProps.location.pathname) {
scrollToTop();
}
}


render() {
return <WrappedComponent {...this.props} />;
}
};
};


export default (WrappedComponent: ComponentType, options?: Options) => {
return withRouter(withScrollToTop(WrappedComponent, options));
};

使用它:

import withScrollToTop from './withScrollToTop';


function MyComponent() { ... }


export default withScrollToTop(MyComponent);

这是另一种方法。

对于react-router v4,你也可以用下面的方式绑定一个监听器来改变历史事件:

let firstMount = true;
const App = (props) => {
if (typeof window != 'undefined') { //incase you have server-side rendering too
firstMount && props.history.listen((location, action) => {
setImmediate(() => window.scrollTo(0, 0)); // ive explained why i used setImmediate below
});
firstMount = false;
}


return (
<div>
<MyHeader/>
<Switch>
<Route path='/' exact={true} component={IndexPage} />
<Route path='/other' component={OtherPage} />
// ...
</Switch>
<MyFooter/>
</div>
);
}


//mounting app:
render((<BrowserRouter><Route component={App} /></BrowserRouter>), document.getElementById('root'));

如果点击链接改变路由,滚动级别也将被设置为0,而没有setImmediate(),但如果用户在浏览器上按下后退按钮,那么它将不起作用,因为浏览器在按下后退按钮时手动将滚动级别重置到上一个级别,所以通过使用setImmediate(),我们使我们的函数在浏览器完成重置滚动级别后执行,从而给我们提供了预期的效果。

你可以使用React路由器dom v4

创建一个scrollToTopComponent组件,如下所示

class ScrollToTop extends Component {
componentDidUpdate(prevProps) {
if (this.props.location !== prevProps.location) {
window.scrollTo(0, 0)
}
}


render() {
return this.props.children
}
}


export default withRouter(ScrollToTop)

或者如果你正在使用标签使用类似下面的东西

class ScrollToTopOnMount extends Component {
componentDidMount() {
window.scrollTo(0, 0)
}


render() {
return null
}
}


class LongContent extends Component {
render() {
<div>
<ScrollToTopOnMount/>
<h1>Here is my long content page</h1>
</div>
}
}


// somewhere else
<Route path="/long-content" component={LongContent}/>

希望这有助于更多关于滚动恢复访问docs hare React路由器dom滚动恢复

值得注意的是onUpdate={() => window.scrollTo(0, 0)}方法已经过时了。

下面是react-router 4+的一个简单解决方案。

const history = createBrowserHistory()


history.listen(_ => {
window.scrollTo(0, 0)
})


<Router history={history}>

但是课程太2018年了

ScrollToTop实现与React挂钩

ScrollToTop.js

import { useEffect } from 'react';
import { withRouter } from 'react-router-dom';


function ScrollToTop({ history }) {
useEffect(() => {
const unlisten = history.listen(() => {
window.scrollTo(0, 0);
});
return () => {
unlisten();
}
}, []);


return (null);
}


export default withRouter(ScrollToTop);

用法:

<Router>
<Fragment>
<ScrollToTop />
<Switch>
<Route path="/" exact component={Home} />
</Switch>
</Fragment>
</Router>

ScrollToTop也可以实现为一个包装器组件:

ScrollToTop.js

import React, { useEffect, Fragment } from 'react';
import { withRouter } from 'react-router-dom';


function ScrollToTop({ history, children }) {
useEffect(() => {
const unlisten = history.listen(() => {
window.scrollTo(0, 0);
});
return () => {
unlisten();
}
}, []);


return <Fragment>{children}</Fragment>;
}


export default withRouter(ScrollToTop);

用法:

<Router>
<ScrollToTop>
<Switch>
<Route path="/" exact component={Home} />
</Switch>
</ScrollToTop>
</Router>

对于较小的应用程序,有1-4个路由,你可以尝试用#id重定向到顶部DOM元素,而不仅仅是一个路由。这样就不需要在ScrollToTop或使用生命周期方法中包装路由。

我想为那些使用react-router-dom v5的人分享我的解决方案,因为这些v4解决方案都没有为我工作。

解决我的问题是安装react-router-scroll-top并将包装器放在<App />中,就像这样:

const App = () => (
<Router>
<ScrollToTop>
<App/>
</ScrollToTop>
</Router>
)

就是这样!它工作!

在主组件中。

只需添加这个React钩子(如果你没有使用React类):

const oldPage = useRef(pathname)


useEffect(() => {
if (pathname !== oldPage.current) {
try {
window.scroll({
top: 0,
left: 0,
behavior: 'smooth'
})
} catch (error) {
// for older browser
window.scrollTo(0, 0)
}
oldPage.current = pathname
}
}, [pathname])
render() {
window.scrollTo(0, 0)
...
}

在道具没有改变和componentDidUpdate()没有触发的情况下,这是一个简单的解决方案。

钩子是可组合的,从React Router v5.1开始,我们就有了useHistory()钩子。所以基于@zurfyx的答案,我为这个功能创建了一个可重用的钩子:

// useScrollTop.ts
import { useHistory } from 'react-router-dom';
import { useEffect } from 'react';


/*
* Registers a history listener on mount which
* scrolls to the top of the page on route change
*/
export const useScrollTop = () => {
const history = useHistory();
useEffect(() => {
const unlisten = history.listen(() => {
window.scrollTo(0, 0);
});
return unlisten;
}, [history]);
};

在你的router.js中,只需要在router对象中添加这个函数。这个就行了。

scrollBehavior() {
document.getElementById('app').scrollIntoView();
},

像这样,

**Routes.js**

import vue from 'blah!'
import Router from 'blah!'


let router = new Router({
mode: 'history',
base: process.env.BASE_URL,
scrollBehavior() {
document.getElementById('app').scrollIntoView();
},
routes: [
{ url: "Solar System" },
{ url: "Milky Way" },
{ url: "Galaxy" },
]
});

React挂钩2020:)

import React, { useLayoutEffect } from 'react';
import { useLocation } from 'react-router-dom';


const ScrollToTop: React.FC = () => {
const { pathname } = useLocation();
useLayoutEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);


return null;
};


export default ScrollToTop;

< >强

反应16.8 +

如果你正在运行React 16.8+,这是一个简单的处理组件,它将在每个导航上向上滚动窗口:
这是scrollToTop.js组件

import { useEffect } from "react";
import { useLocation } from "react-router-dom";


export default function ScrollToTop() {
const { pathname } = useLocation();


useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);


return null;
}
然后在你的应用程序的顶部渲染它,但是在Router
下面 这是在app.js

import ScrollToTop from "./scrollToTop";


function App() {
return (
<Router>
<ScrollToTop />
<App />
</Router>
);
}

或者在index.js中

import ScrollToTop from "./scrollToTop";


ReactDOM.render(
<BrowserRouter>
<ScrollToTop />
<App />
</BrowserRouter>
document.getElementById("root")
);

我的解决方案:我在我的屏幕组件中使用的组件(我想要滚动到顶部)。

import { useLayoutEffect } from 'react';


const ScrollToTop = () => {
useLayoutEffect(() => {
window.scrollTo(0, 0);
}, []);


return null;
};


export default ScrollToTop;

当返回时保持滚动位置。 使用useEffect()对我来说是有bug的,当返回文档时,文档会滚动到顶部,并且当已经滚动的文档中的路由被更改时,也会有闪烁的效果

一个可以添加到Route组件的React Hook。使用useLayoutEffect代替自定义监听器。

import React, { useLayoutEffect } from 'react';
import { Switch, Route, useLocation } from 'react-router-dom';


export default function Routes() {
const location = useLocation();
// Scroll to top if path changes
useLayoutEffect(() => {
window.scrollTo(0, 0);
}, [location.pathname]);


return (
<Switch>
<Route exact path="/">


</Route>
</Switch>
);
}

更新:更新为使用useLayoutEffect而不是useEffect,以减少视觉上的干扰。大致可以理解为:

  • useEffect:渲染组件->绘制到屏幕->滚动到顶部(运行效果)
  • useLayoutEffect:渲染组件->滚动到顶部(运行效果)->绘制到屏幕

这取决于你是否加载数据(想想旋转器)或者你是否有页面转换动画,useEffect可能更适合你。

这是我基于其他人在之前的文章中所做的方法。想知道在2020年使用位置作为依赖来防止重渲染是否是一个好方法吗?

import React, { useEffect } from 'react';
import { useLocation } from 'react-router-dom';


function ScrollToTop( { children } ) {
let location = useLocation();


useEffect( () => {
window.scrollTo(0, 0);
}, [ location ] );


return children
}

因为,我使用功能组件,这里是我如何设法实现它。

import { useEffect } from 'react';
import { BrowserRouter, Routes, Route, useLocation } from 'react-router-dom';


function ScrollToTop() {
const { pathname } = useLocation();


useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);


return null;
}


const IndexRoutes = () => {


return (
<BrowserRouter>
<ScrollToTop />
<Routes>
<Route exact path="/">
<Home />
</Route>
/* list other routes below */
</Routes>
</BrowserRouter>
);
};


export default IndexRoutes;

您也可以从下面的链接中参考代码

https://reactrouter.com/web/guides/scroll-restoration

使用useEffect() -功能组件的解决方案

 useEffect(() => {
window.history.scrollRestoration = 'manual';}, []);

对我来说,window.scrollTo(0, 0)document.documentElement.scrollTo(0, 0)不能在我的所有屏幕上工作(只在一个屏幕上工作)。

然后,我意识到我的屏幕的溢出(允许滚动)不在window中(因为我们有一些静态点,所以我们将overflow: auto放在其他div中)。

为了实现这一点,我做了以下测试:

useEffect(() => {
const unlisten = history.listen(() => {
console.log(document.documentElement.scrollTop)
console.log(window.scrollTop)
window.scrollTo(0, 0);
});
return () => {
unlisten();
}
}, []);

在所有的对数中,我得到0。

所以,我寻找我有滚动的容器,并放置一个id:

<div id="SOME-ID">
...
</div>

在我的ScrollToTop组件中,我放入:

useEffect(() => {
const unlisten = history.listen(() => {
window.scrollTo(0, 0);
document.getElementById("SOME-ID")?.scrollTo(0, 0)
});
return () => {
unlisten();
}
}, []);

现在,当我用history.push("/SOME-ROUTE")去到一个新路线时,我的屏幕会转到顶部

2021年8月,

而不是在每个页面都这样做,你可以在App.js中这样做

import { useLocation } from "react-router-dom";


const location = useLocation();
useEffect(() => {
window.scrollTo(0,0);
}, [location]);

useEffect中设置location将确保在每次路径更改时滚动到顶部。

2021年(React 16) -基于@Atombit的注释

下方卷轴向上,还保留了历史卷轴位置。

function ScrollToTop() {
const history = useHistory()
useEffect(() => {
const unlisten = history.listen((location, action) => {
if (action !== 'POP') {
window.scrollTo(0, 0)
}
})
return () => unlisten()
}, [])
return (null)
}

用法:

<Router>
<ScrollToTop />
<Switch>
<Route path="/" exact component={Home} />
</Switch>
</Router>

利用钩子,你可以简单地在代码库的useEffect中插入window.scrollTo(0,0)。简单地在你的应用程序中实现代码片段,它应该在它的窗口顶部加载每个页面。

import { useEffect } from 'react';


useEffect(() => {
window.scrollTo(0, 0);
}, []);

for react-router-dom V5 react-router-dom V5滚动到顶部

    import React, { useEffect } from 'react';
import {
BrowserRouter as Router,
Switch,
Route,
Link,
useLocation,
withRouter
} from 'react-router-dom'
function _ScrollToTop(props) {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo(0, 0);
}, [pathname]);
return props.children
}
const ScrollToTop = withRouter(_ScrollToTop)
function App() {
return (
<div>
<Router>
<ScrollToTop>
<Header />
<Content />
<Footer />
</ScrollToTop>
</Router>
</div>
)
}

我唯一的解决方案是在每个文件中添加一行代码,例如:

import React from 'react';
const file = () => { document.body.scrollTop = 0; return( <div></div> ) }

我在我的项目中使用了Typescript。这招对我很管用:

  // ScrollToTop.tsx
import {useEffect, useRef} from 'react'
import {withRouter} from 'react-router-dom'


const ScrollToTopComponent = () => {
const mounted = useRef(false)


useEffect(() => {
if (!mounted.current) {
//componentDidMount
mounted.current = true
} else {
//componentDidUpdate
window.scrollTo(0, 0)
}
})


return null
}


export const ScrollToTop = withRouter(ScrollToTopComponent)






// usage in App.tsx
export default function App() {
return (
<Router>
<ScrollToTop />
<OtherRoutes />
</Router>
)
}

平滑滚动选项

import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';


export default function ScrollToTop() {
const { pathname } = useLocation();


useEffect(() => {
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
}, [pathname]);


return null;
}

...

<Router>
<ScrollToTop />
...
</Router>

FOR 'REACT-ROUTER-DOM v6 &上面的

我通过创建一个包装器函数并将其包装在所有路由上解决了以下问题。

请遵循以下步骤:

1:需要导入以下内容:

import {Routes, Route, BrowserRouter as Router, useLocation} from 'react-router-dom';
import {useLayoutEffect} from 'react';

2:写一个包装函数在"应用"功能:

const Wrapper = ({children}) => {
const location = useLocation();
useLayoutEffect(() => {
document.documentElement.scrollTo(0, 0);
}, [location.pathname]);
return children
}

3:现在在包装器函数中包装你的路由:

<BrowserRouter>
<Wrapper>
<Navbar />
<Routes>
<Route exact path="/" element={<Home/>} />
<Route path="/Products" element={<Products/>} />
<Route path="/Login" element={<Login/>} />
<Route path="/Aggressive" element={<Aggressive/>} />
<Route path="/Attendance" element={<Attendance/>} />
<Route path="/Choking" element={<Choking/>} />
<Route path="/EmptyCounter" element={<EmptyCounter/>} />
<Route path="/FaceMask" element={<FaceMask/>} />
<Route path="/Fainting" element={<Fainting/>} />
<Route path="/Smoking" element={<Smoking/>} />
<Route path="/SocialDistancing" element={<SocialDistancing/>} />
<Route path="/Weapon" element={<Weapon/>} />
</Routes>
<Footer />
</Wrapper>
</BrowserRouter>

这应该能解决问题。

对于我的应用程序,当通过链接导航时,有能力向上滚动是很重要的。但在点击标签(也是链接)时,不要滚动任何东西也是很重要的。此外,我的应用程序有一个先进的布局,因此可滚动的容器可以根据当前页面的布局和链接的位置而不同。

下面是我的解决方案:

react-router-domLink中引入RouterLink包装器,该包装器添加了onClick处理程序。当链接被单击时,它会找到最近的滚动容器并向上滚动。可以通过指定preserveScroll(这是原始Link的默认行为)来选择退出此行为。

希望这能有所帮助。

// Helper to find nearest scrolled parent of a given node
const getScrollParent = (node) => {
if (!node) {
return null;
}


if (node.scrollTop > 0) {
return node;
} else {
return getScrollParent(node.parentNode);
}
};


interface RouterLinkProps extends LinkProps {
preserveScroll?: boolean;
}
export const RouterLink: React.FC<RouterLinkProps> = ({ preserveScroll = false, ...linkProps }) => {
const handleClick = useCallback(
(val) => {
const targetEl = val?.target;
if (!targetEl || preserveScroll) {
return;
}
const scrolledContainer = getScrollParent(targetEl);
if (scrolledContainer) {
scrolledContainer.scrollTo({
top: 0,
left: 0,
behavior: 'smooth',
});
}
},
[preserveScroll],
);


const extraProps = useMemo(() => {
if (!preserveScroll) {
return {
onClick: handleClick,
};
}
return {};
}, [handleClick, preserveScroll]);


return <Link {...linkProps} {...extraProps} />;
};

现在,我可以使用这个包装器并获得所需的行为和足够的控制来调整它。是这样的:

<RouterLink to="/some/path">My Link that scrolls up</RouterLink>

2022年11月更新

在react最新版本18.2.0和react-router-dom 6.4.3中没有任何工作。所以我实现了这个。为我工作。希望这对大家有帮助。

ScrollToTop.js

import { useEffect } from "react";
import { useLocation } from "react-router-dom";


export default function ScrollToTop() {
const { pathname } = useLocation();


useEffect(() => {
const body = document.querySelector('#root');
body.scrollIntoView({
behavior: 'smooth'
}, 500)


}, [pathname]);


return null;
}

然后在index.js或App.js中导入并添加到浏览器路由器,在那里定义路由。

import { BrowserRouter, Routes, Route } from "react-router-dom";
import ScrollToTop from "./ScrollToTop";


function App() {
return (
<div>
<BrowserRouter>
<ScrollToTop />
<Routes>
//your routes
</Routes>
</BrowserRouter>
</div>
);
}


export default App;

(注意:确保index.html div id="root".)