导航到另一个页面时出现问题,它的位置将保持与之前的页面相同。所以它不会自动滚动到顶部。我也试过window.scrollTo(0, 0)
在onChange
路由器上使用。我也曾经scrollBehavior
解决过这个问题,但是没有用。对此有什么建议吗?
32 回答
但是2018年的课程是如此
使用 React Hooks 实现 ScrollToTop
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>
此答案仅适用于 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/>
显然这适用于大多数情况,但还有更多关于如何处理选项卡式界面以及为什么没有实现通用解决方案的内容。
反应 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;
}
然后在你的应用程序的顶部渲染它,但在路由器
下面是 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")
);
此答案适用于遗留代码,对于路由器 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);
}
您可以添加一些标志,例如“scrolled = false”,然后进行更新:
componentDidUpdate() {
if(this.scrolled === false){
window.scrollTo(0,0);
scrolled = true;
}
}
一个可以添加到 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
可能对您更有效。
对于react-router v4,这里有一个实现滚动恢复的 create-react-app :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
值得注意的是,该onUpdate={() => window.scrollTo(0, 0)}
方法已过时。
这是 react-router 4+ 的简单解决方案。
const history = createBrowserHistory()
history.listen(_ => {
window.scrollTo(0, 0)
})
<Router history={history}>
反应钩子 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;
在下面的组件中<Router>
只需添加一个 React Hook(如果您没有使用 React 类)
React.useEffect(() => {
window.scrollTo(0, 0);
}, [props.location]);
我的应用程序遇到了同样的问题。使用下面的代码片段帮助我在单击下一个按钮时滚动到页面顶部。
<Router onUpdate={() => window.scrollTo(0, 0)} history= {browserHistory}>
...
</Router>
但是,问题仍然存在于浏览器背面。经过大量试验,意识到这是因为浏览器窗口的历史记录对象,它有一个属性 scrollRestoration 被设置为自动。将此设置为手动解决了我的问题。
function scrollToTop() {
window.scrollTo(0, 0)
if ('scrollRestoration' in history) {
history.scrollRestoration = 'manual';
}
}
<Router onUpdate= {scrollToTop} history={browserHistory}>
....
</Router>
Hooks 是可组合的,从 React Router v5.1 开始,我们有了一个useHistory()
hooks。因此,根据@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]);
};
我想与那些正在使用的人分享我的解决方案,react-router-dom v5
因为这些 v4 解决方案都没有为我工作。
解决我的问题的是安装react-router-scroll-top并将包装器放在<App />
这样的位置:
const App = () => (
<Router>
<ScrollToTop>
<App/>
</ScrollToTop>
</Router>
)
就是这样!有效!
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>
这是我的方法,基于其他人在之前的帖子中所做的。想知道这是否是 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 { useLayoutEffect } from 'react';
const ScrollToTop = () => {
useLayoutEffect(() => {
window.scrollTo(0, 0);
}, []);
return null;
};
export default ScrollToTop;
这会在返回时保留滚动位置。使用 useEffect() 对我来说是错误的,当返回文档时,文档会滚动到顶部,并且在已经滚动的文档中更改路由时也会产生闪烁效果。
利用钩子,您可以简单地插入window.scrollTo(0,0)
您useEffect
的代码库中。只需在您的应用程序中实现代码片段,它就会在其窗口顶部加载每个页面。
import { useEffect } from 'react';
useEffect(() => {
window.scrollTo(0, 0);
}, []);
因为,我使用功能组件,这就是我设法实现它的方法。
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;
您也可以参考以下链接中的代码
2021 年 8 月
而不是在每个页面中执行此操作,您可以在 App.js 中执行此操作
import { useLocation } from "react-router-dom";
const location = useLocation();
useEffect(() => {
window.scrollTo(0,0);
}, [location]);
设置位置useEffect
将确保在每次路径更改时滚动到顶部。
带有平滑滚动选项
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>
对我来说,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")
屏幕前往新路线时,请转到顶部
我写了一个名为withScrollToTop
. 这个 HOC 有两个标志:
onComponentWillMount
- 导航时是否滚动到顶部(componentWillMount
)onComponentDidUpdate
- 更新时是否滚动到顶部 (componentDidUpdate
)。在组件未卸载但发生导航事件(例如 from/users/1
to )的情况下,此标志是必需的/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);
在您的中,只需在对象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-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 router dom scroll recovery
我唯一的解决方案是在每个文件中添加一行代码,例如:
import React from 'react';
const file = () => { document.body.scrollTop = 0; return( <div></div> ) }
使用 useEffect() - 功能组件的解决方案
useEffect(() => {
window.history.scrollRestoration = 'manual';}, []);
我在我的项目中使用了 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>
)
}
对于具有 1-4 条路由的较小应用程序,您可以尝试通过重定向到带有 #id 的顶部 DOM 元素而不只是一条路由来破解它。这样就不需要在 ScrollToTop 中包装 Routes 或使用生命周期方法。
对于 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>
)
}
我发现这ReactDOM.findDomNode(this).scrollIntoView()
是有效的。我把它放在里面componentDidMount()
。
render() {
window.scrollTo(0, 0)
...
}
如果 props 未更改且 componentDidUpdate() 未触发,则可以是一个简单的解决方案。
这是hacky(但有效):我只是添加
window.scrollTo(0,0);
渲染();