One of the great caveats of single-page apps is that they rely on taking over many of the browser’s default behaviours. One of the more significant ones is how the browser behaves on page-to-page navigation, be it forwards or backwards. By default, the browser will take you to the top of the page when you navigate forwards by following a link. In contrast, you expect to be returned to the position on the page where you followed a link, when you use your browser’s back-button.
In case you happen to be using Backbone to power your single-page app, this behaviour can be restored with very little effort and code. Unfortunately, this will only work in browsers supporting the History API, so old-IE (IE 9 and earlier) users are out of luck.
To pull it off, a global navigation event listener is used to store the current scrollTop
just-in-time.
<code>// A global a.onclick event handler for all your navigational needs
// see e.g. Backbone Boilerplate for a more complete example
$(document).on("click", "a", function (ev) {
ev.preventDefault();
// Replace current state before triggering the next route,
// storing the scrollTop in the state object
history.replaceState(
_.extend(history.state || {}, {
scrollTop: document.body.scrollTop || $(document).scrollTop()
}),
document.title,
window.location
);
Backbone.history.navigate(this.pathname, { trigger: true });
});</code>
Listening to the route
event from your Backbone.Router
you then restore scrollTop
when it exists in history.state
, or default to page top. To catch navigation happening via the back- and forward-buttons in the browser, Backbone.history
should be set up to listen for popState
events by setting { pushState: true }
in the options to Backbone.history.start()
.
<code>// Backbone.Routers trigger the route-event
// after the route-handler is finished
var router = new AppRouter(); // AppRouter extends from Backbone.Router
router.on("route", function () {
// Inspect history state for a scrollTop property,
// otherwise default to scrollTop=0
var scrollTop = history.state && history.state.scrollTop || 0;
$("html,body").scrollTop(scrollTop);
});</code>
For those who need to see before they believe, take a look at the demo.
NB. The code examples are simplified to the extreme and shouldn’t be treated as complete drop in code.