NOTE: The proposed API is now part of WhatWG HTML Living Standard

Custom Scroll Restoration Proposal

This is a proposal for a small new API* that gives web authors additional control over user agent’s restoration of persisted use state in particular the scroll position. (whatwg discussion)

* This is implemented in chromium and it is planed for release in version 46.

Problem Summary

History spec does not specify the scroll position persistence and restoration and leaves it up to user agents to implement. Despite this all major browsers provide scroll restoration functionality for both traditional document navigations and entries create via History API (i.e., pushState, and replaceState). Despite its prevalence there is currently no reliable and cross- browser solution for developers to opt out of this behaviour or control any of its aspects.

The default scroll restoring behaviour works well for document style web sites but it is often not appropriate for single-page web applications. Precise controlling of the scroll restoration is particularly important for single- page applications due to the following reasons:

  1. They may be re-constructing the page content onpopstate every time and often asynchronously. The user agent does not know when page is fully constructed so it may attempt to restore scroll position on a partially constructed page which has an incorrect size causing the scroll to land on an unintended incorrect position due to clamping.

  2. They may want to provide consistent scroll restoration regardless of how the page is entered e.g., via history navigation or vanilla links clicks.

  3. They may want to control the details of visual transition between UI states for example using a specific scrolling animation to transition instead of an instant jump, or even restoring scroll position of inner scrollers.

There several hacks used in practice which suffer from serious issues and force developers to make unnecessary compromises. You can find a discussion of these workarounds in appendix A. Few examples for developers struggling with these issues are documented for Mozilla, Facebook, React router, Google products, Mobile SPA, and others.

Existing Workarounds

There are several imperfect ways to work around the lack in the API. These are brittle and have serious drawbacks. Two of the more popular ones are:

As a side not both workarounds require rolling out a custom scroll state persistence and restoration solution which needs to keep track of scroll positions in order to restore them in future. Tracking document scroll position is complicated and brittle because it is not possible to distinguish between browser and user initiated scrolls and browsers have different scroll restoration timing. See this bug for an example of this brittleness that broke Photo Search. Being able to disable browser scroll restoration has the side benefit of making it possible to have a simple and reliable solution for tracking document scroll position (i.e., record scroll position onpopstate).

Alternative APIs Considered

  1. Use events to allow detection of scroll restoration and its prevention. This forces the timing of restoration to be after DOMReady on cross-document navigation. This is an unacceptable UX.
  2. Use a fourth additional options on pushState, replaceState. Deemed too complex for simple scenarios and unnecessarily links state creation with scroll restoration.
  3. Same as #2 but with two brand new methods (push, replace) that accept dictionaries.

For even more minor API variation considered see this document.