Design, CG Graphics & Inspiration
Fixing animation in jQuery

Fixing animation in jQuery

Hi! Initially I was going to write in this article that the jQuery animation engine is rather inefficient and creates a lot of timers, each of them running on its own, which results in the page content being redrawn too often and therefore is very bad for the Web browser’s performance. I was also going to describe some techniques for creating “true” animation. However, while preparing my examples I realized that I was wrong.

Although this is true that the jQuery animation engine is inefficient and brings about a lot of problems, they are caused not by the creation of too many timers but by something else — and I believe I achieved great results in tackling all those problems!

Well, I’ll be telling you about moving objects being out of sync even if all of them are animated by one and the same instance the animate() function.
Let’s take a look at a small example of simultaneous animation of multiple objects:

<div class="container">
<div class="animate"></div>
</div>
<script type="text/javascript">// <![CDATA[
  $(function(){
    for (var i = 0; i < 8; i++) {
      $('.animate').clone().appendTo('.container');
    }
    $('.animate').animate({
      left: 800
    }, 500);
  });
// ]]></script>

Although in theory all the objects must move in sync with each other, in practice it looks like that:

Sure, it’s just a synthetic test, while in reality the parent of those objects would be moved, but the result is obvious — animations created by calling the same instance of animate() can start and end at different times! It would be logical to assume that a separate timer is created for each element of animation, but, as I’ve already said, this assumption is wrong. Having looked a little bit into the source code, I realized that one call of the animate() function creates only one timer for animating all the elements. Moreover, only one timer is used for creating all the animations on the Web page made using jQuery, and that timer is deleted following the completion of the last animation on the page and is re-created whenever something else must be animated.

Nonetheless, the real cause of mistiming lies pretty close to my wrong assumption, the large number of timers, and it is that each object’s animation start time is calculated independently from others. Thus, if the number of elements is large enough, a considerable time may pass between the moments of start time calculation for the first element and for the last one. This time difference will be the more the slower the Web browser’s JavaScript engine, which we can see clearly on the screenshot: in Opera, the maximum misplacement of elements amounts to 110 pixels (the size of the picture is one half of the actual screenshot’s size); in Chrome, it’s 74 pixels; and in one very popular browser, it even exceeds the width of the “test run” (800 pixels).

Beside the misalignment of multiple animated objects, sometimes you can also see a single object’s properties being out of sync, caused by the same thing. For example, the sum of an object’s padding and width, which in theory must stay constant, in practice may vary, resulting in the object’s edge being unstable.

Now, here’s the most important thing: As only one timer is involved, fixing the animation start time was no sweat; it took me only a couple of minutes to make the patch which solves the problem — now the animation start time is calculated only once, upon calling animate(). I even went further and added a new parameter, startTime, to the animate() function’s options, which can take the user-defined animation start time and thus makes it possible to synchronize any number of calls of animate().

Besides being used for synchronization, the startTime parameter lets you do some interesting things:
1) To set the animation start time in the past and then to start the animation in the middle. It’s handy because now you don’t have to use a separate easing function for half of the animation.

2) To set the animation start time in the future in order to give enough time for the slower JavaScript engines (hi, IE!) to process all the objects. However, an easing function must be used in this case, to bring the negative time differences to zero, as the standard linear and swing do not let you do that.

3) To make an animation in several passes or to create an endless animation, while setting the animation start time in the future. To do that, you need to use an easing function capable of correct processing of a negative time difference. As the standard easing function, swing, is based on Math.cos, it will do.

Special note: Easing functions take values between 0 and 1 as their arguments (where 0 is the start time, and 1 is the end time), and their return values also lie between 0 and 1 (where 0 is the beginning value of the variable property, and 1 is its final value). Generally, the properties may lie outside the 0 to 1 interval; if my patch is applied, the time can be negative, too.

My modifications to the code

n the jQuery.fx.step function, the current step’s time was calculated as follows:
var t = now();
I added one more parameter, time, into the function, and changed the definition of time t in the following way:
var t = time || now();
I’ve had to add a new parameter, startTime, into the jQuery.fx.custom function, and calculate the animation start time as it was done in the previous function:
this.startTime = startTime || now();
Besides, I added the getting of time within the only setInterval:
var time = now();
with further passing of that time obtained to other functions of the jQuery.timers[] array, which contains the jQuery.fx.step functions.

The optall object of the animate() function was supplemented with the startTime parameter:
optall = jQuery.extend({startTime: now()}, optall)
Frankly speaking, my patch is extremely useful for myself, so if I don’t find any problems with that I’ll be using it at least in my own projects. What troubles me is if the patch is that simple why didn’t the jQuery developers themselves make something similar after they’ve done a much greater work on designing a single timer for all animation. Is it possible that there’s some logic in that, while I just fail to see something? What do you think, is it worthwhile to try offering my patch for the framework?

Patched jQuery

Update. Actually, I’ve already found the major pitfall in using my technique, and it manifests itself when a queue is used. However, the problem doesn’t look hopeless, and I guess I can find a way around it. As soon as I’m sure that my solution is bug-free (or cannot be implemented at all :) ), I’ll inform the jQuery author on that.

P.S. The other day I also found out that Opera 9.2 and the latest jQuery versions don’t mix together well enough: transparency may not work. Description of the problem and a temporary fix.

  • Splashnology Editors,
  • November 8, 2009

SHARE THIS POST

This post has been written by the team here at Splashnology.com

Subscribe for the hottest posts

Subscribe to our email newsletter for useful tips and freebies.