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

时间:2016-04-28 02:26:15

标签: javascript reactjs react-router react-router-redux

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

27 个答案:

答案 0 :(得分:58)

React Router v4的文档包含用于滚动恢复的code samples。这是他们的第一个代码示例,当页面导航到以下时,它作为“滚动到顶部”的站点范围解决方案:

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

^ copied directly from the documentation

显然这适用于大多数情况,但更多关于如何处理选项卡式界面以及为什么通用解决方案尚未实现。

答案 1 :(得分:26)

此答案适用于旧版代码,适用于路由器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”,然后在update:

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

答案 2 :(得分:21)

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

答案 3 :(得分:21)

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

答案 4 :(得分:14)

您可以将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可能会更好。

答案 5 :(得分:11)

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

答案 6 :(得分:9)

请注意,onUpdate={() => window.scrollTo(0, 0)}方法已过时。

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

const history = createBrowserHistory()

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

<Router history={history}>

答案 7 :(得分:7)

我的应用程序遇到了同样的问题。使用下面的代码片段帮助我点击下一个按钮滚动到页面顶部。

{{1}}

然而,问题仍然存在于浏览器上。经过大量的试验,意识到这是因为浏览器窗口的历史对象,它有一个属性scrollRestoration,设置为auto.Setting这个手动解决了我的问题。

{{1}}

答案 8 :(得分:6)

React hooks 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;

答案 9 :(得分:5)

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

答案 10 :(得分:3)

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

import { useLayoutEffect } from 'react';

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

    return null;
};

export default ScrollToTop;

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

答案 11 :(得分:3)

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

解决我问题的方法是安装react-router-scroll-top,然后将包装器放入<App />中,

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

就是这样!它起作用了!

答案 12 :(得分:2)

我写了一个名为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);

答案 13 :(得分:1)

<Router>下的组件中

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

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

答案 14 :(得分:1)

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

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

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

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") 前往新路线时,我的屏幕会转到顶部

答案 15 :(得分:1)

由于我使用了函数组件,因此我设法实现了它。

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

答案 16 :(得分: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}/>

希望这有助于在滚动恢复方面提供更多帮助,尤其是文档野兔react router dom scroll restoration

答案 17 :(得分: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'));

如果通过单击链接更改了路径,但是如果用户在浏览器上按下后退按钮,则滚动级别将设置为0而不会setImmediate(),但是当浏览器将滚动级别手动重置为按下后退按钮时的上一级,因此通过使用setImmediate(),我们会在浏览器完成后执行我们的功能,重置滚动级别,从而为我们提供所需的效果。

答案 18 :(得分: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" },
    ]
});

答案 19 :(得分:0)

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

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

答案 20 :(得分:0)

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

答案 21 :(得分:0)

这是我基于其他人在以前的帖子中所做的工作的方法。想知道这是否会在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
}

答案 22 :(得分:0)

使用 useEffect() - 函数式组件的解决方案

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

答案 23 :(得分:0)

2021 年 8 月

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

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

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

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

答案 24 :(得分:0)

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>

答案 25 :(得分:-1)

我发现ReactDOM.findDomNode(this).scrollIntoView()正在发挥作用。我将它放在componentDidMount()内。

答案 26 :(得分:-5)

这很hacky(但有效):我只是添加

window.scrollTo(0,0);

to render();