Backbone Browser Back Button Detection

How to detect a browser back button press in a Backbone app and trigger directional transitions.

See the Pen Backbone Back Button Detection by Larry Naman (@LarryAnomie) on CodePen.



In a recent Backbone project, I needed a way to reverse a page transition if a user was navigating backwards through the app. To explain, I have pages animating in right to left when a user clicks a UI component that triggers a view change, but need to reverse this (have pages animate in left to right) if the user clicks the browser back button – much the same way as native app transitions behave.

Check out the demo, or grab the code.

TL;DR

Keep a record of all routes hit as a user navigates through your app in an array and hook into router.execute to compare the new route with the previous but one route to determine if we are going backwards or forwards.

The longer explanation

There’s a few things going on in the demo here so let’s take them step by step.

First we need to set a flag whenever a user clicks a UI element that triggers a view change. If this flag is set to true then we know that it wasn’t a back button click that triggered the view change. We attach this event when we initialize our app, setting a flag on the app instance to true if a link was clicked:


            $(document).on('click', 'a', function(e) {
                self.linkClicked = true;
            });

As part of the app initialization process we set some flags that will be useful to use later:


        routes: [], // an array to hold our route history
        linkClicked : false, // an app wide flag recording if a link was clicked
        backDetected : false, // a flag to indicate if user is going back

Next we need to find somewhere to hook into a route change where we can figure out if we are going backwards or forwards. Initially I looked at some of the numerous plugins that extend Backbone’s rather basic router, but then I came across Router.execute – a method added in the 1.1.x release of Backbone. Here’s a description of Router.execute from the Backbone docs:

This method is called internally within the router, whenever a route matches and its corresponding callback is about to be executed. Return false from execute to cancel the current transition. Override it to perform custom parsing or wrapping of your routes, for example, to parse query strings before handing them to your route callback

This is the perfect place to figure out our direction of travel. First we grab the current route and reset our backDetected flag:


            var route Backbone.history.getFragment();
            app.backDetected = false;

Then we check whether the new route matches the previous but one route and whether a link has been clicked (remember that if a link has triggered the view change, we know that the browser back button wasn’t pressed). If these conditions are true then we know we are going backwards and can remove the last route from our routes array (the place the user is clicking back from) to prevent the user getting stuck between two urls.

            if (app.routes[app.routes.length - 2] === route && !app.linkClicked) {

                app.backDetected = true;
                app.routes.pop();

            }

Finally we need to reset our linkedClicked flag, ready for the next transition.

app.linkClicked = false;

Now that we know which way the user is navigating through the app, we can pass this parameter to our page views:

params = _.extend(params, {
    reverseAnimation: app.backDetected
});

app.instance.goto(view, params);
And then use this param to decide which side to animate in from:

        /**
         * js animate in
         * @param  {Object} options - animation config options
         * @param  {Boolean} options.reverseAnimation - right to left?
         */
        animateIn: function(callback, options) {

            var self = this,
                xPercent = '100%',
                tween;

            if (options && options.reverseAnimation) {
                xPercent = '-100%';
            }

            tween = TweenMax.fromTo(self.$el, self.inDuration, {
                xPercent: xPercent
            }, {
                xPercent: '0%',
                ease: self.ease,
                onUpdateParams: ['{self}', 'param2'],
                onComplete: function() {
                    callback.apply(this);
                }
            });

        }

In this case I’m using TweenMax for animation, but you could equally use this technique to decide which classes to apply to your view to trigger a CSS transition.

Inspired by

This approach builds on a few techniques:

Leave a Reply