Jonathan Snook’s jQuery Background Animation as a Plugin

Jonathan Snook recently posted a really neat background animation technique using jQuery. This was something I was looking for and it seemed like a good candidate for a jQuery plugin.

So, following on from my recent post about turning jQuery code into richer, unit testable plugin code, I thought I’d describe the quick process of doing so here. (It’s worth reading Snook’s post first though!)

The general steps discussed to achieve this are as follows:

  1. Add additional keyboard accessibility
  2. A first attempt plugin
  3. A plugin that might be unit testable

Add additional keyboard accessibility

I posted a comment on Snook’s post to say additional hover and blur events should do the trick, so here is an example applied to one of the four demo menus he had with those events added:

$('#d a')
    .css( {backgroundPosition: "0 0"} )
    .mouseover(function(){
        $(this).stop().animate({backgroundPosition:"(0 -250px)"}, {duration:500})
    })
    .mouseout(function(){
        $(this).stop().animate({backgroundPosition:"(0 0)"}, {duration:500})
    })
// I added these event handlers:
    .focus(function(){
        $(this).stop().animate({backgroundPosition:"(0 -250px)"}, {duration:500})
    })
    .blur(function(){
        $(this).stop().animate({backgroundPosition:"(0 0)"}, {duration:500})
    })

A first attempt plugin

The above code as a plugin might look something like this:

(function($) {
    $.fn.animatedBackground = function(options) {

    // build main options before element iteration by extending the default ones
    var opts = $.extend({}, $.fn.animatedBackground.defaults, options);

    function startAnimation() {
        $(this).stop().animate(
           {backgroundPosition:opts.backgroundPositionStart},
           {duration:opts.duration}
        );
    }
    
    function stopAnimation() {
        var animationConfig = { duration:opts.duration };
        if (opts.complete)
            animationConfig.complete = opts.complete;

        $(this).stop().animate(
            {backgroundPosition:opts.backgroundPositionEnd},
            animationConfig
        );
    }
    
    // for each side note, do the magic.
    return $(this)
        .css( {backgroundPosition: opts.backgroundPositionInit} )
        .mouseover(startAnimation)
        .mouseout(stopAnimation)
        .focus(startAnimation)
        .blur(stopAnimation)
    };

    // plugin defaults
    $.fn.animatedBackground.defaults = {
        backgroundPositionInit : "0 0",
        backgroundPositionStart : "(0 0)",
        backgroundPositionEnd : "(0 0)",
        durationStart : 500,
        durationEnd : 500,
        complete : null
    };
})(jQuery);

The above is just a quick 2 minute thing — I am sure with more thought the plugin options could be made even more flexible. But this will do for the purpose of this post.

For each of the 4 demo menus Snook provided, you could then call them as follows:

$(function(){
    $('#a a')
        .animatedBackground(
            {
                backgroundPositionInit : "-20px 35px",
                backgroundPositionStart : "(-20px 94px)",
                backgroundPositionEnd : "(40px 35px)",
                durationEnd : 200,
                complete : function(){
                    $(this).css({backgroundPosition: "-20px 35px"});
                }
            }
        );
        
    $('#b a')
        .animatedBackground(
            {
                backgroundPositionStart : "(-150px 0)",
                backgroundPositionEnd : "(-300px 0)",
                durationEnd : 200,
                complete : function(){
                    $(this).css({backgroundPosition: "0 0"});
                }
            }
        );

    $('#c a, #d a')
        .animatedBackground(
            { backgroundPositionStart : "(0 -250px)" }
        );
});

(Examples c and d are combined with one selector, while a and b each have more complex options.)

In the “simple” cases (c and d) a very small amount of code is needed to use the plugin. For (a and b) if you were only going to use this once, it might be questionable whether the plugin for this is worth the effort!

Unit testable plugin?

Some plugins might be so small that unit testing them may not seem beneficial or worth the effort. In this particular case, it is not clear if it is necessary. However, for the purpose of this post at least it may be a useful exercise. So, these might be some things to bear in mind:

  • The bulk of the plugin relies on animate() which works asynchronously. Unit testing asynchronous calls can be tricky with QUnit. More importantly, we are not trying to unit test animate() but our plugin code instead.
  • The function handler for each mouse/focus/blur event could be made into a default plugin function
  • Unit tests can then replace the default function with a mock function to confirm that the rest of the plugin works with the various configuration options passed in.

To achieve the above, a simple step might just be to make the private startAnimation() and stopAnimation() methods public.

This can be done a few ways, e.g. keep those private methods and make them call the public ones, or wherever the private ones are called, make them call the public ones, etc.

The two public methods would look something like this:

$.fn.animatedBackground.startAnimation = function($el, opts) {
    $el.stop().animate(
        {backgroundPosition:opts.backgroundPositionEnd},
        {duration:opts.duration}
    );
}

$.fn.animatedBackground.stopAnimation = function($el, opts) {
    var animationConfig = { duration:opts.duration };
    if (opts.complete)
        animationConfig.complete = opts.complete;

    $el.stop().animate(
        {backgroundPosition:opts.backgroundPositionEnd},
        animationConfig
    );
}

Here’s a page with a unit testable version of the plugin which also has the original menu examples

Was it worth adding extra code to make it unit testable?

The testable plugin version is a bit larger than the original (ignoring minification and gzipping benefits to remove a lot of the difference).

Was it therefore worth changing in this way from the original?

In my opinion, the initial plugin version would probably suffice, especially if likely to be used across a few small projects.

If, on the other hand, you were going to use it in a more critical scenario, then unit testing what you can could be useful.

A principle of test driven development is to write unit tests first. In this case as it was existing code, it seemed okay to do it in the order described above. Furthermore, sometimes it feels tricky to always stick to that principle religiously, and writing unit tests afterwords might be okay if the plugin is smallish, perhaps?

Summary

So, many thanks for Jonathan Snook for his post. That technique is useful for me in some other projects.

This post hopefully shows that even small snippets of code can be turned into a plugin, sometimes unit testable ones. Whether that is worth your efforts depends on your need and audience.

9 thoughts on “Jonathan Snook’s jQuery Background Animation as a Plugin

  1. i am using this plug in my page as a test
    i want to know
    what shuld i do if i want to make an contiunes on going effect of moving background rather than on mouse over

    my code is like this

    $(document).ready(
    function(){
    $(‘div.DivBackUpperImage’).css( {backgroundPosition: “0px 0px”} )
    $(‘div.DivBackUpperImage’).animate({backgroundPosition:”(1000px 0px)”}, {duration:4000})
    }
    );

    how to check if the background position has reached 1000xp to restart moving for start so that it keep rotation all the time

    actually you wrote a very nice plug in and helps me create many effects on mouse over click etc but this … i cant understand i m little new to jquery…

    please any help would be appriciable
    thanks

  2. Hi anup shah thanks for the advice….
    i get id working a little but still its not looping back around its stoping i dnt know the callback is not working or what … can you please correct…

    my code is like this

    $(function(){
    $(‘div.DivBackUpperImage’).css( {backgroundPosition: “0px 0px”} )
    recursiveFunction();
    });

    function recursiveFunction(){
    $(‘div.DivBackUpperImage’).animate({backgroundPosition:”(1000px 0px)”}, {duration:4000},
    function(){
    $(‘div.DivBackUpperImage’).css( {backgroundPosition: “0px 0px”} )
    recursiveFunction();
    }
    );
    }

    i think it should work but… its not …
    thanks for the helo

  3. Hi,

    It looks like you are using the wrong version of animate(). You can call animate with a number of parameters. If you pass in two object literals like you have then there isn’t a third option of a callback. Instead, of passing { duration:4000 } you can just pass in 4000. In that override of animate, then the callback works.

    This should work:

    $(function(){
      $('div.DivBackUpperImage').css({
        backgroundPosition: "0px 0px"
      });
      recursiveFunction();
    });
    
    function recursiveFunction(){
      $('div.DivBackUpperImage').animate({
        backgroundPosition:"(1000px 0px)"},
        4000, function(){
          $('div.DivBackUpperImage').css( {
            backgroundPosition: "0px 0px"
          });
          recursiveFunction();
        });
    }
    

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.