Skip to content

Automatic browser scrolling corrupts router scroll history #707

@agundermann

Description

@agundermann

As far as I can tell the implementation of the scrolling behaviors is flawed. But first of all, some steps to reproduce:

  1. Run and open up the "master-details" example (Chrome or Firefox)
  2. Scroll down a bit
  3. Click on Ryan (or any other link)
  4. Click browser back button
  5. Click browser forward button

After 5), the expected behavior would be to scroll to the top again. Instead, the router will scroll to the position set in 2).

Some console.logs to understand what's going on (Chrome):

1) Initial render "/"
Router.run, current scroll: {"x":0,"y":0}

2) Manual scrolling "/"
scroll event to: {"x":0,"y":100} 

3) Clicking link from "/" to "/contacts/ryan"
Scroll History is now: {"/":{"x":0,"y":100}}
Router.run, current scroll: {"x":0,"y":100}
ImitateBrowserBehavior: scrolling to 0, 0
scroll event to: {"x":0,"y":0}

4) Back button from "/contacts/ryan" to "/"
// this is where things start to fall apart..
// expected: {"/":{"x":0,"y":100},"/contact/ryan":{"x":0,"y":0}} 
Scroll History is now: {"/":{"x":0,"y":100},"/contact/ryan":{"x":0,"y":100}}
Router.run, current scroll: {"x":0,"y":100}
ImitateBrowserBehavior: scrolling to 0, 100
scroll event to: {"x":0,"y":100}

5) Forward button from "/" to "/contacts/ryan"
Scroll History is now: {"/":{"x":0,"y":0},"/contact/ryan":{"x":0,"y":100}}
Router.run, current scroll: {"x":0,"y":0}
ImitateBrowserBehavior: scrolling to 0, 100
scroll event to: {"x":0,"y":100}

The problem is that browsers (at least Firefox and Chrome) will restore their own recorded scroll positions before triggering any events (I believe this is the same for hashchange and popstate), causing the router to store these newly set positions for the previous path. (Things are even worse if you render asynchronously..)

Some thoughts about solving this problem

Since there's no "beforepopstate", the first workaround that occurred to me was to listen to the scroll event and store from there (instead of on dispatch). Unfortunately, browsers will trigger the scroll event themselves after back button navigation and it seems there is no reasonable way to distinguish them from user-initiated scroll events.

So I thought about simply ignoring scroll events for a short time after popstate. Problem with that is that Firefox triggers the scroll event even before popstate. Chrome, for comparison, triggers it afterwards, but restores the scroll position beforehand, and it seems to back off if the scroll position was manually set before popstate finishes (hence there's only one scroll event after back/foward in the above log).

Those problems made me think that maybe the router should not do anything on popstate events, and people should make sure to fully restore content synchronously (at least after popstate). Sadly, Firefox doesn't play along: since it restores the scroll position before popstate, and apparently doesn't do anything afterwards, it will clamp scroll positions to the previous/current page size, i.e. when navigating from a small to a taller page, it will not scroll beyond the small page's height.

Is this really such a mess? Hopefully I'm missing something here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions