Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scroll management #3950

Closed
nhagen opened this issue Sep 26, 2016 · 26 comments
Closed

Scroll management #3950

nhagen opened this issue Sep 26, 2016 · 26 comments

Comments

@nhagen
Copy link

nhagen commented Sep 26, 2016

Loving the v4 API currently! Wanted to start a thread on this so the community can stay updated. From the FAQ:

What about scrolling?

We have some code close to being published that will manage the scroll positions of window and individual elements.

Whose working on this, and whats the ETA for this? Will it still be provided by the react-router-scroll library?

@mjackson mjackson changed the title Scroll in v4 [v4] Scroll management Sep 27, 2016
@mjackson mjackson added this to the v4.0.0 milestone Sep 27, 2016
@mjackson
Copy link
Member

I have a POC on my machine, but it requires #3956 to be fixed before it works.

@mjackson mjackson changed the title [v4] Scroll management Scroll management Sep 27, 2016
@mjackson mjackson self-assigned this Sep 27, 2016
@ryanflorence
Copy link
Member

@mjackson it is fixed! just not released yet.

@xzilja
Copy link
Contributor

xzilja commented Nov 9, 2016

@ryanflorence Any updates on this? Scroll management would be good to have in a router :)

@ericvrp
Copy link

ericvrp commented Dec 7, 2016

ScrollManagement on v4 still open or fixed? I to would my page to show the same after router.goBack()

@cjke
Copy link

cjke commented Dec 19, 2016

As a temporary solution this SO Question + Accepted answer have worked for me: http://stackoverflow.com/questions/40217585/in-react-router-v4-how-does-one-link-to-a-fragment-identifier

@timdorr
Copy link
Member

timdorr commented Feb 1, 2017

@mjackson Are we deferring to the browsers on this or putting something together (or updating your POC)?

@mjackson
Copy link
Member

mjackson commented Feb 2, 2017

I'll see if I can push an updated POC soon.

@steida
Copy link
Contributor

steida commented Feb 6, 2017

Party pooper here. In case of next POC will take next several months, maybe this could help meanwhile: https://github.com/4Catalyzer/found-scroll

@mjackson
Copy link
Member

mjackson commented Feb 6, 2017

😂 @steida, you're such a helpful ray of sunshine. thank you!

@mjackson
Copy link
Member

mjackson commented Feb 6, 2017

Whoa, just looked at found and ... found some code that is eerily similar to my code. Creepy.

@mjackson
Copy link
Member

OK, I'm going to try and find an elegant solution to this problem this week for (at least the DOM bindings in) v4. Up to this point I've focused all my attention on making sure we had a good core, and now that we're shipping v4 betas every few days I think we're there.

If you're interested in trying to scratch your own itch and solve this problem for your app, but you've been hesitant because v4 was too unstable, you should feel free to give it a shot now too.

For folks who are interested, we've got quite a backstory that you can follow right here in this repo. Here are a few links that should help you get a feel for research that has already been done in previous versions:

And for everyone who thinks "oh my gosh I can't believe they don't have an answer for this yet" I refer you to #707 (comment):

Is this really such a mess?

Yes.

If you're in the middle of a project with complex scroll management requirements right now, I'd love to hear about it and/or see your proposed solutions. The v4 API is much more modular than past versions, so it should be a lot easier than it used to be to get in and alter how the router works simply by inserting components into the tree. I've got a POC that currently preserves the scroll position on document.body, but some apps have more strenuous requirements than that.

/cc @gaearon @ryanflorence @taurose because reading through those old issues made me remember those days when we were just getting started on this project very fondly 💕

@luke-john
Copy link

We decided to adopt react-router 4 around the second alpha and went to production at around alpha-5

The key stakeholders of our site were happy for us to land with a solution that simply scrolled to top on a forward navigation, which was very simple to implement with the v4 API. 👍

We are currently looking at updating to the beta and as part of that we will be looking at implementing the following scroll behaviors.

Upon navigating to a content page, scrolling to the content in the page (skipping header)

Ensuring on backwards navigation, with fresh state being used to render, the user is ether;

  • if state change is not related to main content -> send to the same section rather than exact scroll position.
  • if state change is related to main content -> send to top of content where update will be obvious

The v4 api has been a pleasure to work with so far.

@mjackson
Copy link
Member

Related: scroll restoration is coming to browsers through a proposed addition to the HTML5 history API. Something to keep in mind.

@apostolos
Copy link
Contributor

apostolos commented Feb 14, 2017

Poor man's scroll restoration:

On lwjgl.org we code-split (webpack 2) each route so we have to wrap each route component with https://github.com/LWJGL/lwjgl3-www/blob/master/client/routes/asyncRoute.js

It was natural to put the scroll restoration code there. Our approach is very simple and it uses the generated location.key to remember the scroll position. If we get a POP action we restore the previous scroll position otherwise we scroll to top. It works really well for a simple content website.

Caveats:

  • It does not implement a limit on scroll locations stored. (should it?)
  • Loses state if page is reloaded, but I suppose it can easily be extended to use sessionStorage.
  • Loses state when navigating to external links. This probably needs a scroll listener similar to scroll-behavior.
  • On first navigation location.key prop is undefined. I don't know if that's by design or a bug. We work around it by giving it a default value of "root".

A cleaned-up version of the HOC can be found here: https://gist.github.com/apostolos/7001146c5437368eef2d281f9ecfb3c8

@cjke
Copy link

cjke commented Feb 14, 2017

We're in the final week before launching a beta of our next app that relies heavily on RR4 (we've been confident enough in the RRv4 changes that warranted using a alpha/beta version in our app).

As mentioned above, the accepted solution in this question http://stackoverflow.com/questions/40217585/in-react-router-v4-how-does-one-link-to-a-fragment-identifier has been working perfectly for us - we've had no issues with it so far, and works well.

I understand its only a piece of the pie, as it addresses only the jump to a fragment identifier part of the problem, but is still useful regardless.

@ryanflorence
Copy link
Member

ryanflorence commented Feb 20, 2017

I wanted to jot down some thoughts on scroll management. I've implemented it several times as the router (and its beta releases) have changed over the last couple of years. I've implemented it as components outside of the router, inside of the router, inside of the history lib, and with raw DOM.

Do we need it?

I'm not sure we need to do anything about it (😂). 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.

Scroll Restoration Spec: https://majido.github.io/scroll-restoration-proposal/history-based-api.html#web-idl

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. That's easy, this will scroll up your entire app on every navigation:

class ScrollToTop extends Component {
  componentDidUpdate(prevProps) {
    if (this.props.location !== prevProps.location) {
      window.scrollTo(0, 0)
    }
  }

  render() {
    return null
  }
}

const ScrollToTopOnNav = () => (
  <Route component={ScrollToTop}/>
)

// Then render it at the top of your app
const App = () => (
  <div>
    <ScrollToTopOnNav/>
  </div>
)

Or you could do it in just the components that need it when they mount:

class ScrollToTopOnMount extends Component {
  componentDidMount(prevProps) {
    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}/>

So what about a more robust generic solution? 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!)

For a generic API I was headed toward this:

<Router>
  <ScrollRestoration>
    <div>
      <h1>App</h1>

      <RestoredScroll id="bunny">
        <div style={{ height: '200px', overflow: 'auto' }}>
          I will overflow
        </div>
      </RestoredScroll>
    </div>
  </ScrollRestoration>
</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 to 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 usually people just want to scroll to the top (which you saw is trivial to add to your app on your own).

So, yeah ... I'm not sure we need it but that API I just showed above would be pretty straightforward to implement (assuming you have a good wrapper around session storage!). I no longer feel strongly enough to do the work myself (plenty other things for me to work on!) but I'd love to help anybody who feels inclined. A solid solution would even live in this monorepo and be an official package. Hit me up if you get started on it :)

@mjackson
Copy link
Member

@ryanflorence Maybe we just add <ScrollToTop> to react-router-dom with some docs that link back to this issue and call it good?

@Andreyco
Copy link

I second @mjackson's opinion. <ScrollToTop> component is enough, it will cover most of everyone's needs. If someone needs specific/non-default behaviour, there is high change it's project specific and RR has no change to cover that. As mentioned, browsers are starting to support scroll restoration API natively, we should make use of that instead.

@mjackson mjackson removed their assignment Mar 8, 2017
@mjackson
Copy link
Member

mjackson commented Mar 8, 2017

This would make a great PR for someone who wants to go for it. Removing the 4.0.0 milestone because I don't consider this blocking for v4.

@mjackson mjackson removed this from the v4.0.0 milestone Mar 8, 2017
@megamaddu
Copy link

megamaddu commented Mar 8, 2017

This is what we've been using for a few weeks now: https://gist.github.com/spicydonuts/d1ca3d78b9448004455562af1f04f81e

It's not real pretty but it behaves close to how you'd expect the browser to, all contained in one component. It also means other components which get location passed in (withRouter, Route, etc) can inspect that scroll state.

@ryanflorence
Copy link
Member

Alright, doc is written so I can close this now :)

@cjke
Copy link

cjke commented Mar 11, 2017

The docs look really awesome. Can I confirm that scrolling to a fragment identifier is outside of scope of the docs, the library and this issue?

@rafgraph
Copy link
Contributor

rafgraph commented Mar 14, 2017

For basic scroll to #hash-fragment functionality I created a HashLink component that you can install from npm (it wraps RRv4's Link component). This is based on the solution for RRv2/3.

Live example: http://react-router-hash-link.rafrex.com
Repo: https://github.com/rafrex/react-router-hash-link

When you click on a link created with react-router-hash-link it will scroll to the element on the page with with the id that matches the #hash-fragment in the link.

$ npm install --save react-router-hash-link
// In YourComponent.js
...
import { HashLink as Link } from 'react-router-hash-link';
...
// Use it just like a RRv4 link:
<Link to="/some/path#with-hash-fragment">Link to Hash Fragment</Link>

@ryanflorence
Copy link
Member

maybe it varies by browser, but with <BrowserRouter/> hash links just work for me.

@rafgraph
Copy link
Contributor

rafgraph commented Mar 16, 2017

@ryanflorence, it doesn't seem to work for me in chrome/safari/firefox. I created a branch of the example site using Browser Router and the Link component to test it out. Can you give it a try and let me know if you get different results. Thanks.
https://github.com/rafrex/react-router-hash-link/tree/browser-router-link

$ git clone -b browser-router-link https://github.com/rafrex/react-router-hash-link.git
$ npm install
$ npm start

The site will be available on port 8080

@trevorr
Copy link

trevorr commented Oct 24, 2018

In case anyone finds it useful, I created a React scroll management library to address the various issues mentioned here:

  1. Scrolling to top on navigation to a new page (aka the easy part)
  2. Scroll restoration on history back/forward, with support for delayed/asynchronous rendering
  3. Scrolling to a specific element when navigating to a hash link, also with support for delayed/asynchronous rendering

Please give it a try and submit issues/PRs if you find any issues:

https://github.com/trevorr/react-scroll-manager

It targets React 16 and supports React Router 4, though it's actually agnostic to the router (except that it indirectly uses the history library underlying React Router). It's based on the ideas behind @ryanflorence's react-router-restore-scroll component for React Router 3 and @gajus's #394 (comment) solution for hash link scrolling.

@lock lock bot locked as resolved and limited conversation to collaborators Dec 23, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests