Animation Comparison of 3 Top Libraries

There are many conversations between designers and engineers that go back and forth about the performance of animations in JavaScript. Often there is an expectation of animations to run consistently (same speed, same step size, same in all browsers, etc.), and as awesome as that would be, this pushes at the limitations of JavaScript.

Many designers experience a great working animation on a demo page, and in turn, expect that the animation can be plugged into a complex JavaScript library, whilst preserving its performance.

Again, as wonderful as this would be, there are a few things that need further explanation. Todays article will look at a simple animation in 3 top libraries: YUI, jQuery, and Prototype/Scriptaculous; and show how uniformly all the animations work well on a simple page, but degrade as the page becomes busier.

Many of you probably know that there are a number of issues affecting how well an animation works in JavaScript: how busy the client machine is, how much CPU/memory your browser dedicates to JavaScript, how much JavaScript is needed to run a given page, and how much work the JavaScript is already doing; to name a few. Also, animation libraries use a combination of the setTimeout or setInterval methods of JavaScript, which are notoriously inaccurate (+/- 15ms), and/or (new Date()).milliseconds() to attempt to smooth animation, but it is fairly easy for an entire step in the animation to be skipped. The summary is that the more complex a page gets the less consistent that animations become.

I choose to compare these three libraries because: YUI is my library of choice, as it was designed by engineers for engineers; Prototype/Scriptaculous is probably the most used library (thanks to ruby on rails and cakePHP); and jQuery is the most popular designer-used library. However, I expect that GWT, Mootools, Dojo, Mochikit, and all the others to behave about the same.

---

For this experiment we are building 3 animations that increase the width of an element from 100px to 400px, and then the reverse, using each library independently. Fortunately, both YUI and jQuery use self-contained namespaces, so they dont conflict with Prototype. Here is the code required for each:

Example 1: jQuery

var isOpenedJquery = false,
    isJQueryAnimated = false;

var jQueryCallback = function() {
    if (! isJQueryAnimated) {
        isJQueryAnimated = true;
        isOpenedJquery = ! isOpenedJquery;
        jQuery(#containerAnimateJQuery).animate({width: isOpenedJquery ? 400px : 100px}, 1000 * globalDuration, null, function() {isJQueryAnimated = false;});
    }
};

jQuery(#triggerAnimateJQuery).click(jQueryCallback);

Example 2: Prototype

var isOpenedPrototype = false,
    isAnimatedPrototype = false;

var prototypeCallback = function() {
    if (! isAnimatedPrototype) {
        isOpenedPrototype = ! isOpenedPrototype;
        isAnimatedPrototype = true;
        Effect.BlindDown(containerAnimatePrototype, {duration: globalDuration, scaleY: false, scaleX: true, scaleFrom: isOpenedPrototype ? 100 : 400, scaleTo: isOpenedPrototype ? 400 : 100, afterFinish: function() {isAnimatedPrototype = false;}, afterUpdate: function() {YAHOO.util.Dom.setStyle(containerAnimatePrototype, height, 20em);}});
    }
};

$(triggerAnimatePrototype).observe(click, prototypeCallback);

Example 3: YUI

var isOpenedYui = false;
var containerAnimate = {
    inYUI: new YAHOO.util.Anim(containerAnimateYUI, {width: {to: 100}}, globalDuration),
    outYUI: new YAHOO.util.Anim(containerAnimateYUI, {width: {to: 400}}, globalDuration)
};

var yuiCallback = function() {
    if (! (containerAnimate.outYUI.isAnimated() || containerAnimate.inYUI.isAnimated())) {
        isOpenedYui = ! isOpenedYui;
        containerAnimate[isOpenedYui ? outYUI : inYUI].animate();
    }
};

YAHOO.util.Event.on(triggerAnimateYUI, click, yuiCallback);

All implementations use a click event callback on a button to trigger animating. There is a global globalDuration variable that can be adjusted on the demo page; YUI and Prototype/Scriptaculous use seconds, while jQuery uses milliseconds. The callback function first checks to ensure that we are not already animating, which is done by setting a boolean via a callback for Prototype/Scriptaculous and jQuery, and calling the isAnimated method on the Anim object in YUI. Then we use a isOpened boolean to determine whether to animate in or out. In Prototype/Scriptaculous and jQuery we call 1-off methods that runs the animation, whereas in YUI we create Anim objects and call the animate methods.

Take a look at the demo, here.

---

What I Learned

  • I still don&rsquot;t like Prototype/Scriptaculous. It took me forever to get the animation working (hours, compared to minutes with the other libraries). *Thanks to Artyom prototype is now working correctly.
  • Prototype/Scriptaculous and jQuery use a linear progression by default, while YUI uses uniform steps by default.
  • All 3 libraries have a variety of easing methods to accelerate/decelerate the step size
  • They all work great under no-load, and fairly well under load. They occasionally glitch, but tend to be rather minor.
  • Matt Sweeney, of YUI, has provided a sinoidal easing method for us, so that YUI animates about the same as the other libraries.