-
-
Notifications
You must be signed in to change notification settings - Fork 10.7k
Description
As far as I can tell the implementation of the scrolling behaviors is flawed. But first of all, some steps to reproduce:
- Run and open up the "master-details" example (Chrome or Firefox)
- Scroll down a bit
- Click on Ryan (or any other link)
- Click browser back button
- 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.