我有一个问题,当导航到另一个页面,它的位置将保持像之前的页面。所以它不会自动滚动到顶部。 我也试过使用window。onChange路由器上的scrollTo(0,0)。我还使用了scrollBehavior来修复这个问题,但它没有工作。对此有什么建议吗?
当前回答
平滑滚动选项
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>
其他回答
对于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上,我们可以检查位置路径名何时发生变化,并将其与路径道具匹配,如果满足,则恢复窗口滚动。
这个方法最酷的地方在于,我们可以有恢复滚动的路由和不恢复滚动的路由。
下面是一个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;
从上面的代码中,有趣的是,只有在导航到/关于或/另一个页面时,才会执行滚动到顶部的操作。然而,当进行/没有滚动恢复将发生。
整个代码库可以在这里找到:https://github.com/rizedr/react-router-scroll-top
一个可以添加到Route组件的React Hook。使用uselayouteeffect而不是自定义监听器。
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,您还可以以以下方式将侦听器绑定到历史事件中的change:
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'));
如果点击链接改变了路由,那么没有setimmediation(),滚动级别也会被设置为0,但是如果用户在浏览器上按下后退按钮,那么它将不起作用,因为浏览器在按下后退按钮时手动将滚动级别重置到上一个级别,所以通过使用setimmediation(),我们使我们的函数在浏览器完成重置滚动级别后执行,从而给我们提供了预期的效果。
对于我的应用程序,当通过链接导航时,有能力向上滚动是很重要的。但在点击标签(也是链接)时,不要滚动任何东西也是很重要的。此外,我的应用程序有一个先进的布局,因此可滚动的容器可以根据当前页面的布局和链接的位置而不同。
下面是我的解决方案:
为react-router-dom添加onClick处理程序的Link引入RouterLink包装器。当链接被单击时,它会找到最近的滚动容器并向上滚动。可以通过指定preservesroll(这是原始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">我向上滚动的链路</RouterLink> .
反应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")
);