Home

Abusing jQuery.animate for fun and profit (and bacon)

Sep 22, 2008

The days of static UIs that only have jarring transitions between pages are pretty much over. With frameworks like CoreAnimation or jQuery, it's easy to add useful animations to applications and webpages. In the case of jQuery, you can easily animate any CSS property, and you get free work-arounds for browser bugs to boot. You can run multiple animations (of arbitrary duration) at the same time, queue animations and even animate complex properties like colors or clipping rectangles.

But what if you want to go beyond mere CSS? You might have a custom widget that is drawn using <canvas>, whose contents are controlled by internal variables; maybe you're using 3D transformations to scale and position images on a page, and simple 2D tweening just doesn't cut it.

In that case, it would seem you are out of luck: jQuery's .animate() method can only be applied to a collection of DOM elements, and relies heavily on the browser's own semantics for processing CSS values and their units. However thanks to JavaScript's flexibility and jQuery's architecture, we can work around this, and re-use jQuery's excellent animation core for our own nefarious purposes.

Hackity hack hack

First, we need an object to store all the variables we wish to animate. We use an anonymous <div> outside of the main document, so that jQuery's DOM calls still work on it. We simply add our own properties to it:

var vars = $.extend($('<div>')[0], {
  foo: 1,
  bar: 2,

  customAnimate: true,
  updated: true
});

In this case, our properties are foo and bar. We also set customAnimate and updated to identify this object (see below).

Next we need to override jQuery's default step function, which gets called for every step of an animation, and applies new values to an element's CSS properties.

   // jQuery.fx.step._default
    _default: function(fx) {
      fx.elem.style[fx.prop] = fx.now + fx.unit;
    }

We can replace it using the following snippet:

var $_fx_step_default = $.fx.step._default;
$.fx.step._default = function (fx) {
  if (!fx.elem.customAnimate) return $_fx_step_default(fx);
  fx.elem[fx.prop] = fx.now;
  fx.elem.updated = true;
};

With the new step function, jQuery will check for the presence of a customAnimate property on any element it is animating. If present, it will assign the (unit-less) value to element.property rather than element.style.property and mark the element by setting element.updated to true.

Now we're ready to animate, using the normal $.animate syntax:

$(vars).animate({ foo: 5, bar: 10 }, { duration: 1000 });

The values vars.foo and vars.bar will now smoothly change over time. You can use any of jQuery's animation abilities as usual.

Now what about that updated variable? Well to actually use these animated values, you will need some kind of timer or step callback to read them back and draw them on the page. If you're using <canvas>, you need to redraw your entire widget for every change, but you don't want to be wasting CPU time by constantly refreshing it. Furthermore, if you're running multiple animations at the same time, you'll want to aggregate all your property changes into a single redraw per frame. This is easy with the updated property and a simple timer:

setInterval(function () {
  if (!vars.updated) return;
  vars.updated = false;
 
  drawWidget();
}, 30);

Now your widget will only refresh itself when its values are changed by the animation step function we defined earlier, and very few CPU cycles are wasted. As a plus, you can render updates as fast or as slow as you want, without affecting the duration of your animations.

Demo

I whipped up a quick demo which renders a cloud of bacon using <canvas>. All the motion in the demo is created through $.animate(), with a bunch of animations running at once.

This demo will not work in Internet Explorer, and has only been tested in Firefox 3 and Safari 3.

This is a rather esoteric example, but there are plenty of useful ways to apply this technique. I've used it to implement smooth, beautiful, usable widgets. You can combine multiple motion and opacity animations triggered by clicks and hovers without issues.

Final notes

While this technique works great, there is one big caveat. You should avoid animating any property that exists in CSS ('float', 'display', 'opacity', ...) because these have unexpected side effects depending on the browser.

There are also a couple of weaknesses:

  • jQuery does not support continued easing. That is, when you override an animation that is already in progress, the variable being animated will instantly stop and restart from its current position. The rate of change is not continuous between the two animations.
  • Animating angles is tricky. E.g. when animating from 350˚ to 0˚, you want it to animate across 10˚ and not the long way around. This requires manual correction.

And obviously, it would be cleaner if jQuery's animation core was refactored to separate out the CSS-specific code instead...

Awesome

Sep 23, 2008 Carsten

This is the best demo ever!

mmmm Bacon....

Sep 24, 2008 Curtis

Very cool stuff, I'm going to have to play with canvas now...and bacon.

Suggestion

Sep 24, 2008 John Resig

How about something like this?

jQuery.fx._default = function(fx) {
  if ( fx.elem.style )
    fx.elem.style[fx.prop] = fx.now + fx.unit;
  else
    fx[fx.prop] = fx.now;
}

and instead of updated you could do:

jQuery(vars).animate({foo: 2, bar: 3}, {
  duration: 1000,
  step: function(now, fx){ /* this == vars */ }
});

With the step callback getting called on every step of the animation.

@John: multiple animations

Sep 24, 2008 Steven

I did mention the use of a custom step callback in the blog post... but I found it makes it harder to do multiple, arbitrary animations, because you need to juggle your different step functions. Unless I'm mistaken, step functions are specific to a particular invocation of $.animate().

For example, in the bacon demo, the 'move bacon' button spawns 10 animations, each with a random duration. The camera's position moves by animating the X, Y and Z position independently. I wouldn't want to call my draw() function for every single step callback. However, there is no single 'master animation' that could take responsibility for every other animation.

I also figured it was cleaner to fall back on jQuery's default step function rather than replace it completely (as in, replace the least amount of code possible), though in this case the different is probably just academic ;).

Godlike :) !!!

Nov 05, 2008 Anonymous

Godlike :) !!!

Amazing...

Feb 24, 2009 Giulio

it's amazing...but I cannot see it with IE...

.click

Feb 25, 2009 Giulio

Hi,
what about if I wanna add a mouse interaction?
thanks in advantage,
Giulio

Hmmmmmm Bacon!!!

Mar 27, 2009 Vim

Nice post, I'll have a taste of that bacon, is that bacon or smoked... I'll try the J.Query hack and put it to the test.

IE 8 not rendering

Apr 30, 2009 Tsalagi

Does anyone else have an issue with IE 8 not rendering the page? Is there a fix?

Thanks

IE 8 appologize

Apr 30, 2009 Tsalagi

I'm sorry I posted the previous post about IE8 before I read all of the blog. I see it doesn't work. Has anyone found a work aroud? This would work great for jumping images I need for a movie website.

Awesome

Jun 01, 2009 Bobbink - 11 Internet

Awesome hack but as you can read above my comment, it doesn't work in Internet Explorer 8. Does someone have a solution?

Technique works in IE, demo doesn't

Jun 01, 2009 Steven

The animation technique works fine in Internet Explorer. It's just that I didn't want to be bothered making the demo work in IE because this particular demonstration uses Canvas for rendering.

Seriously people, instead of all whining about IE, why not actually spend a whopping 10 minutes to try implementing this yourself? I posted this because it's an interesting approach for advanced animation coding, not because I want to hold everyone's hand.

E.g. this is the sort of stuff I use it for. Code or GTFO.

That's awesome.

Jun 15, 2009 scupper

You're amazing. I want to lift all of your code. :) Nothing to contribute yet but I'm experimenting with your jQuery hack. You win a bookmark in my bookmarks bar!

Brilliant

Sep 07, 2009 Leszek Swirski

I've been looking for something exactly like this, and it would have taken me a long time to come up with the "customAnimate" hack. Also, "Hackity hack hack" is the best heading ever.

very nice website, very nice demonstration

Dec 02, 2009 Mario Allegro

i am very impressed :-)

Awesome.

Dec 17, 2009 Drew Diller

You, sir, are a gentleman and a scholar. Code yes, all well and good. However, this just has good taste.

Boooh ho ho.

:|

Jan 22, 2010 Amos

i wish i could useful... but "this is the best jquery demo i've seen"

is all can think of..

wtf ?

Mar 14, 2010 doss

awesome demo, really impressive...

Post new comment

Note: all posts containing spam will be removed.
The content of this field is kept private and will not be shown publicly.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <b> <dd> <dl> <dt> <i> <li> <ol> <u> <ul> <img> <em> <p> <br> <span> <div> <h2> <h3> <abbr> <small> <table> <tr> <td> <strong> <acronym> <th> <blockquote>
  • Lines and paragraphs break automatically.
  • You may post code using <code>...</code> (generic) or <?php ... ?> (highlighted PHP) tags.

More information about formatting options

Recent comments

Images