In a single page application (SPA), the application manipulates the browser history and DOM to simulate navigation. Because navigation is simulated and rendering is dynamic, the usual browser behavior of restoring scroll position when navigating back and forth through the history is not generally functional. While some browsers (particularly Chrome) attempt to support automatic scroll restoration in response to history navigation and asynchronous page rendering, this support is still incomplete and inconsistent. Similarly, SPA router libraries provide varying but incomplete levels of scroll restoration. For example, the current version of React Router does not provide scroll management, and older versions did not provide support for all cases.
This library attempts to provide this missing functionality to React applications in a flexible and mostly router-agnostic way. It supports saving per-location window and element scroll positions to session storage and automatically restoring them during navigation. It also provides support for the related problem of navigating to hash links that reference dynamically rendered elements.
This library has the following requirements:
- HTML5 browsers: Only the browser history API (not hash history) is supported. Generally, this means modern browsers or IE 10+.
- React 16 and higher: The modern Context API is used.
The following features of newer browsers are supported with fallbacks for older browsers:
- If MutationObserver is not available (e.g. IE 10), the library will fall back to polling.
- Scroll restoration
will be set to
manual
if available, and ignored if not (e.g. IE and Edge).
npm install react-scroll-manager
The following example demonstrates usage of this library with React Router v4. It includes scroll restoration for both the main content window and a fixed navigation panel.
import React from 'react';
import { Router } from 'react-router-dom';
import { ScrollManager, WindowScroller, ElementScroller } from 'react-scroll-manager';
import { createBrowserHistory as createHistory } from 'history';
class App extends React.Component {
constructor() {
super();
this.history = createHistory();
}
render() {
return (
<ScrollManager history={this.history}>
<Router history={this.history}>
<WindowScroller>
<ElementScroller scrollKey="nav">
<div className="nav">
...
</div>
</ElementScroller>
<div className="content">
...
</div>
</WindowScroller>
</Router>
</ScrollManager>
);
}
}
The ScrollManager component goes outside of your router component. It enables manual scroll restoration, reads and writes scroll positions from/to session storage, saves positions before navigation events, handles scrolling of nested components like WindowScroller and ElementScroller, and performs delayed scrolling to hash links anywhere within the document. It has the following properties:
Name | Type | Required | Description |
---|---|---|---|
history | object | yes | A history object, as returned by createBrowserHistory or createMemoryHistory . |
sessionKey | string | no | The key under which session state is stored. Defaults to ScrollManager . |
timeout | number | no | The maximum number of milliseconds to wait for rendering to complete. Defaults to 3000. |
The WindowScroller component goes immediately inside your router component. It handles scrolling the window position after navigation. If your window position never changes (e.g. your layout is fixed and all scrolling occurs within elements), it need not be used. It has no properties, but must be nested within a ScrollManager.
The ElementScroller component goes immediately outside of a scrollable component (e.g. with overflow: auto
style)
for which you would like to save and restore the scroll position. It must be nested within a ScrollManager and has
the following required property:
Name | Type | Required | Description |
---|---|---|---|
scrollKey | string | yes | The key within the session state under which the element scroll position is stored. |
Always be sure to use your router library's link component rather than <a>
tags when navigating to hash links.
While a link like <a href="#id">
will navigate to the given element on the current page, it bypasses the usual
call to history.pushState
, which assigns a unique key
to the history location. Without a location key, the library has no way to associate the position with the location,
and scroll restoration won't work for those locations.
<Link to="#id">...</Link> <!-- right way -->
<a href="#id">...</a> <!-- wrong way -->
- The concept for this library is based on react-router-restore-scroll.
- The timed MutationObserver approach to scrolling to hash link elements comes from Gajus Kuizinas.
- Thanks to Anders Gissel for suggesting a fix for IE 11 window scrolling and Søren Bruus Frank for submitting TypeScript definitions.
react-scroll-manager
is available under the ISC license.