Matt Snider JavaScript Resource

Understanding JavaScript and Frameworks

Wednesday, October 22, 2008

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. Today’s 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 don’t 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’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.
posted by Matt Snider at 10:39 am  

5 Comments »

  1. It seems that Prototype and Scriptaculous works right, but you use it wrong way.
    BlindDown effect has it’s opposite effect - BlindUp. First effect must be used when you want to make element appear, second - when you want to make element disappear. If you want to toggle this effects, you should use Effect.toggle() instead.

    scaleFrom and scaleTo is a percentage values in range 0%–100% and you try to use 400%.
    Effect.BlindDown works like this: it sets the element’s dimension to scaleFrom (0% default) and increases element’s dimension step-by-step to scaleTo (100% default). And then it cleans element’s style (width or height), because scaleTo defaults to 100, but you can use afterFinish handler to avoid this and set the value you want.
    Effect.BlindUp works opposite way and in the end of BlindUp effect element’s display style sets to none.

    The right way to use BlindUp and BlindDown effects in your test page is:

    div#containerAnimatePrototype {
    width: 40em;
    }

    Prototype

    var isOpenedPrototype = false,
    isAnimatedPrototype = false;

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

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

    Comment by Artyom Makarov — October 22, 2008 @ 3:14 pm

  2. Oh, sorry, I don’t know that html tags would be stripped.

    I want to say, that you need to set div#containerAnimatePrototype width to 40em in css (’cause you want 400%) and display: none inline.
    And change one line in your javascript code to this:

    Effect.toggle(’containerAnimatePrototype’, ‘blind’, {duration: globalDuration, scaleY: false, scaleX: true, scaleFrom: isOpenedPrototype ? 25 : 100, scaleTo: isOpenedPrototype ? 100 : 25, afterFinish: function() {isAnimatedPrototype = false;}, afterUpdate: function() {YAHOO.util.Dom.setStyle(’containerAnimatePrototype’, ‘height’, ‘20em’);}});

    It’s not the same animation as for JQuery or YUI, but looks like :-)

    Comment by Artyom Makarov — October 22, 2008 @ 3:23 pm

  3. Thanks Artyom,

    That explains why I had so much trouble. The APIs say that you can use a percentage or a number, but maybe they mean that the number is converted into a percentage. Do you know an animation effect that I can use to just increase/decrease the width of an element?

    Comment by Matt Snider — October 22, 2008 @ 3:56 pm

  4. You can use an Effect.Tween (http://github.com/madrobby/scriptaculous/wikis/effecttween) when you want to create your own effects like this.

    Working example is:

    new Effect.Tween(’containerAnimatePrototype’, isOpenedPrototype ? 100 : 400, isOpenedPrototype ? 400 : 100, {duration: globalDuration, afterFinish: function() {isAnimatedPrototype = false;}}, function(value) { $(’containerAnimatePrototype’).setStyle({width: value + ‘px’})} );

    I’ve test this code under IE6, FF3 and Opera 9.25. It works properly ;)

    Comment by Artyom Makarov — October 23, 2008 @ 2:35 am

  5. Thanks again Artyom,

    I have updated the demo to use your animation.

    Comment by Matt Snider — October 23, 2008 @ 2:59 pm

RSS feed for comments on this post. TrackBack URI

Leave a comment

Powered by WordPress