Delayed scroll restoration in Firefox using our polyfill.

Maintaining scroll positions in all browsers

A few months ago I was riding the subway taking positions on the Brigade web app. I noticed something strange. Whenever I didn’t have a strong opinion I checked out the reasons people had left to inform myself. Then I would use the back button to take positions again, only to have to scroll down all the way to where I was before. That was a bad experience—I had to do a lot of scrolling—especially since I like to take a lot of positions!

Existing solutions

After digging through the Chrome source code, I found the piece of code responsible for restoring the scroll position. It turns out that Chrome uses some sophisticated logic to determine when to restore the scroll position, which looks at if network requests have completed, if the size of the page has changed since last looking at it, if the user has already scrolled, and so on. A personal highlight is the variable canRestoreWithoutAnnoyingUser.

It turns out that this is such a debated topic in the react-router community, that they even split out a separate repo for different scroll behaviours.

Looking at how other people solve this problem, I found that our navigation library at the time, pjax, had functionality for caching page content when navigating away, so that pages can be immediately restored when navigating back. However, it assumed having static content, and didn’t seem to play well with React, which we use for our rendering, as React components would lose their state.

Writing our own polyfill

So I decided to write my own solution. I really liked Chrome’s behaviour, so I wanted to write a polyfill that would emulate it as closely as possible. A key part of Chrome’s algorithm was to wait until the page becomes large enough to actually be able to scroll to previous position again, and that seemed easy enough to implement. Instead of waiting for network requests to finish, I simply checked the page width and height every few milliseconds, and that seemed to work well enough. Then, when it would be possible to scroll, do it. With manual testing of scroll values, that seemed to work pretty well.

Interestingly, people have argued that this is a bug in the spec, and in fact Chrome has deliberately implemented this differently.

Those modifications worked well for navigating to new pages, but not so much for pressing “back” and then “forward” again, because we didn’t save the scroll position from before clicking the “back” button. So I wanted to also store the scroll position when popState was fired, because that means that the browser navigates to another place. However, it turns out that’s not so easy, because the spec states that the scroll position should be restored before firing popState, so we would not be able to determine the actual scroll position. Interestingly, people have argued that this is a bug in the spec, and in fact Chrome has deliberately implemented this differently. So I had to do without scroll restoration when pressing the “forward” button.

Materialistic minimalist. Optimistic realist. Creative copycat. Rationalises believing in paradoxes.

Materialistic minimalist. Optimistic realist. Creative copycat. Rationalises believing in paradoxes.