在每次转换时,将react-router滚动到顶部


112

导航到另一个页面时出现问题,其位置将保持与以前的页面相同。所以它不会自动滚动到顶部。我也尝试window.scrollTo(0, 0)在onChange路由器上使用。我还使用了scrollBehavior来解决此问题,但没有成功。有什么建议吗?


您能否componentDidMount在新路线的组件中加入逻辑?
Yuya

只需添加document.body.scrollTop = 0;componentDidMount你移动到组件
约翰·拉德尔

@Kujira我已经在componentDidMount()里面添加了scrollTo,但是没有用。
阿德里安·哈坦托(Adrian Hartanto)

@JohnRuddell那也没有用。
阿德里安·哈坦托(Adrian Hartanto)

我必须使用document.getElementById('root')。scrollTop = 0才能正常工作

Answers:


119

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/>

^直接从文档中复制

显然,这适用于大多数情况,但是还有更多关于如何处理选项卡式接口以及为何尚未实现通用解决方案的信息。


3
您还应该在滚动到顶部的同时重置键盘焦点。我写了一件事来照顾它:github.com/oaf-project/oaf-react-router
danielnixon

3
这些文档摘录Because browsers are starting to handle the “default case” and apps have varying scrolling needs (like this website!), we don’t ship with default scroll management. This guide should help you implement whatever scrolling needs you have.让我很伤心,因为它们提供代码示例的所有选项实际上都可以通过属性设置包含在控件中,并且可以更改一些默认行为。恕我直言,这感觉超级骇人且未完成。意味着只有高级React开发人员才能正确进行路由,而没有#pitOfSuccess
snowcode

2
oaf-react-router似乎正在解决所有work-arounds此处都忽略的重要可访问性问题。@danielnixon +100
snowcode

未定义ScrollToTop。如何在App.js中导入它?从'./ScrollToTop'导入ScrollToTop不起作用。
anhtv13

@ anhtv13,您应该提出一个新问题,以便您可以包含正在引用的代码。
mtl

92

但上课是如此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>

2
到目前为止,我在互联网上看到的最佳解决方案。我只是有点困惑,为什么反应路由器的项目没有一个属性添加scrollToTop<Route>。它似乎是非常常用的功能。
stanleyxu2005

干得好。帮助很大。
VaibS

进行单元测试的合适方法是什么?
马特

你应该检查一下action !== 'POP'。侦听器接受动作作为第二个arg
Atombit

同时,你可以使用useHistory挂钩,而不是withRouter HOC
Atombit

29

此答案用于旧代码,对于路由器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;
  }
}

我已经完全不使用getDomNode来更新答案,因为实际上我从来不需要使用它来滚动到顶部。我刚刚使用过window.scrollTo(0,0);
Lukas Liesis '16

3
onUpdate钩在react-router v4中已弃用-只是要指出这一点
Dragos Rizescu

@DragosRizescu关于使用此方法而不带钩的任何指导onUpdate吗?
马特·沃达

1
@MattVoda您可以听听历史本身的变化,查看示例:github.com/ReactTraining/history
Lukas Liesis

3
@MattVoda在下面写了一个关于如何使用react-router-v4实现此目标的答案
Dragos Rizescu

28

对于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我们可以检查位置路径名何时更改并将其与pathprop 匹配,如果满意,则恢复窗口滚动。

这种方法的妙处在于,我们可以拥有恢复滚动的路由和不恢复滚动的路由。

这是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


1
正是我想要的!我必须将ScrollTo添加到ScrollToTopRoute的componentDidMount中,因为我的路由包裹在Switch组件中(我还删除了if,因为我希望它在每个安装架上触发)
tocallaghan

在ScrollToTopRoute的呈现中,无需指定render = {props =>(<Component {... props} />))}。只需在路线中使用{... rest}即可。
Finickyflame

该答案为我提供了用于实现解决方案和修复错误的所有工具。非常感谢。
Null isTrue'true

它不能完全正常工作。如果您点击项目链接,则默认为打开Home。现在滚动到的底部,Home然后转到“ AnotherPage”,然后不滚动。现在,如果您再次访问Home,则该页面将滚动到顶部。但是根据您的回答,home页面应保持滚动状态。
aditya81070

18

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

这是适用于react-router 4+的简单解决方案。

const history = createBrowserHistory()

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

<Router history={history}>

如果使用历史记录,这应该是正确的答案。它涵盖了所有可能的事件,包括单击指向您当前所在页面的链接。我发现的唯一问题是scrollTo没有触发,需要包装在setTimeout(window.scrollTo(0,0),0);中。我仍然不明白为什么,但是嘿
-sidonaldson

2
添加#并添加?到我们的网址末尾时,您会遇到问题。在第一种情况下,您不应该滚动到顶部,在第二种情况下,您根本不应该滚动
Arseniy-II,

1
不幸的是,这不适用于BrowserRouter。
VaibS

12

如果您运行的是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")
);

7

我的应用程序也遇到了同样的问题。使用下面的代码段可以帮助我单击下一步按钮滚动到页面顶部。

<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>

6

在下面的组件中 <Router>

只需添加一个React Hook(以防您不使用React类)

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

4

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

解决我的问题的是安装react-router-scroll-top并将包装器放入<App />如下所示:

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

就是这样!有效!


4

挂钩是可组合的,并且自从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]);
};

4

您可以将React Hook添加到Route组件。使用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可能对您更好。


3

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

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

返回时可以保留滚动位置。使用useEffect()对我来说是个错误,当回退时文档将滚动到顶部,并且在已经滚动的文档中更改路径时也会产生闪烁效果。


3

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;

你能解释一下这部分吗?const ScrollToTop: React.FC = () => {我不明白这ScrollToTop: React.FC是什么意思
beeftosino

1
打字稿定义
Kepro

2

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

  • onComponentWillMount-是否在导航时滚动到顶部(componentWillMount
  • onComponentDidUpdate-是否在更新时滚动到顶部(componentDidUpdate)。在未卸载组件但发生导航事件(例如,从/users/1to)的情况下,此标志是必需的/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);

1

这是另一种方法。

对于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'));

setImmediate()如果通过单击链接更改了路线,则滚动级别也不会设置为0,但是如果用户按下浏览器上的“后退”按钮,则它将不起作用,因为当按下“后退”按钮时,浏览器会手动将滚动级别重置为上一级,因此通过使用setImmediate()我们,我们的功能将在浏览器完成重置滚动级别后执行,从而达到预期的效果。


1

使用React Router 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}/>

希望这有助于为更多的滚动恢复VIST有文档野兔反应路由器DOM滚动恢复



0

对于具有1-4条路由的小型应用程序,您可以尝试使用#id重定向到顶部DOM元素,而不是仅通过一条路由来破解它。这样就无需将Routes包装在ScrollToTop中或使用生命周期方法。


0

在您的中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" },
    ]
});

-1
render() {
    window.scrollTo(0, 0)
    ...
}

在不更改道具且未触发componentDidUpdate()的情况下,这可能是一个简单的解决方案。


-11

这很hacky(但可以):我只是添加

window.scrollTo(0,0);

渲染();


2
因此它会在每次状态更改和重新呈现时向上滚动,而不是在导航更改时向上滚动。只需检查一下我的答案即可
Lukas Liesis
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.