Scroll Restoration

在 React Router 早期版本中我们提供了大家期待已久的滚动恢复开箱即用(out-of-the-box) 功能. 希望这篇文档能够帮助你获得 In earlier version of React Router we provided out-of-the-box support for scroll restoration and people have been asking for it ever since. Hopefully this document helps you get what you need out of the scroll bar and routing!

Browsers are starting to handle scroll restoration with history.pushState on their own in the same manner they handle it with normal browser navigation. It already works in chrome and it’s really great. Here’s the Scroll Restoration Spec.

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.

Scroll to top

Most of the time all you need is to “scroll to the top” because you have a long content page, that when navigated to, stays scrolled down. This is straightforward to handle with a <ScrollToTop> component that will scroll the window up on every navigation:

  1. class ScrollToTop extends Component {
  2. componentDidUpdate(prevProps) {
  3. if (this.props.location !== prevProps.location) {
  4. window.scrollTo(0, 0)
  5. }
  6. }
  7. render() {
  8. return this.props.children
  9. }
  10. }
  11. export default withRouter(ScrollToTop)

Then render it at the top of your app, but below Router

  1. const App = () => (
  2. <Router>
  3. <ScrollToTop>
  4. <App/>
  5. </ScrollToTop>
  6. </Router>
  7. )
  8. // or just render it bare anywhere you want, but just one :)
  9. <ScrollToTop/>

If you have a tab interface connected to the router, then you probably don’t want to be scrolling to the top when they switch tabs. Instead, how about a <ScrollToTopOnMount> in the specific places you need it?

  1. class ScrollToTopOnMount extends Component {
  2. componentDidMount(prevProps) {
  3. window.scrollTo(0, 0)
  4. }
  5. render() {
  6. return null
  7. }
  8. }
  9. class LongContent extends Component {
  10. render() {
  11. <div>
  12. <ScrollToTopOnMount/>
  13. <h1>Here is my long content page</h1>
  14. </div>
  15. }
  16. }
  17. // somewhere else
  18. <Route path="/long-content" component={LongContent}/>

Generic Solution

For a generic solution (and what browser are starting to implement natively) we’re talking about two things:

  1. Scrolling up on navigation so you don’t start a new screen scrolled to the bottom
  2. Restoring scroll positions of the window and overflow elements on “back” and “forward” clicks (but not Link clicks!)

At one point we were wanting to ship a generic API. Here’s what we were headed toward:

  1. <Router>
  2. <ScrollRestoration>
  3. <div>
  4. <h1>App</h1>
  5. <RestoredScroll id="bunny">
  6. <div style={{ height: '200px', overflow: 'auto' }}>
  7. I will overflow
  8. </div>
  9. </RestoredScroll>
  10. </div>
  11. </ScrollRestoration>
  12. </Router>

First, ScrollRestoration would scroll the window up on navigation. Second, it would use location.key to save the window scroll position and the scroll positions of RestoredScroll components to sessionStorage. Then, when ScrollRestoration or RestoredScroll components mount, they could look up their position from sessionsStorage.

What got tricky for me was defining an “opt-out” API for when I didn’t want the window scroll to be managed. For example, if you have some tab navigation floating inside the content of your page you probably don’t want to scroll to the top (the tabs might be scrolled out of view!).

When I learned that chrome manages scroll position for us now, and realized that different apps are going to have different scrolling needs, I kind of lost the belief that we needed to provide something—especially when people just want to scroll to the top (which you saw is straight-forward to add to your app on your own).

Based on this, we no longer feel strongly enough to do the work ourselves (like you we have limited time!). But, we’d love to help anybody who feels inclined to implement a generic solution. A solid solution could even live in the project. Hit us up if you get started on it :)