/dev/nikc/blog

Kuolleiden purjehduskenkien seura

Shorts

Dec 3rd 2013

Managing scrollTop in your Backbone single-page app

19:04

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.

// 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 });
});

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().

// 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);
});

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.

Meta

Pages

Search blog

Latest comments