Quantcast
Channel: onenaught.com » JavaScript
Viewing all articles
Browse latest Browse all 8

Jonathan Snook’s jQuery Background Animation as a Plugin

$
0
0

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.


Viewing all articles
Browse latest Browse all 8

Trending Articles