Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2008 (Washington, D.C.) with:

Using jQuery's animate() Method To Power Easing-Based Iteration

By Ben Nadel on

Last night, I was reading about jQuery animation when I had the thought that you could use the animate() method to power iteration in which the iteration-step was implemented with an easing function rather than with a liner incrementation. A while back, I demonstrated that the step callback of the animate() method could be used to create complex, custom animation; but, I think you could abstract that concept out even further to create animate-powered iteration. Now, why would you want to do this? I have no idea; but, it seemed like a fun thing to try.

In the following code, I am adding the ease() function to the jQuery namespace:

jQuery.ease( start, end, duration, easing, callback )

To relate this concept back to for-loop iteration, you can sort of think of the above as being equivalent to the following pseudo code:

  • for (var i = start ; i <= end ; i = easing()){
  • callback( i );
  • }

When the callback gets invoked for each step of the iteration, I am passing in the following parameters:

  • Index: The current value of the iteration index.
  • StepIndex: The current iteration (increments 1 for each callback invocation).
  • EstimatedSteps: The rough estimation of how many times the callback will be invoked.
  • Start: The original start value.
  • End: The original end value.

To see this in action, take a look at the following code:

  • <!DOCTYPE html>
  • <html>
  • <head>
  • <title>Using jQuery's Animate / Easing For Iteration</title>
  • <script type="text/javascript" src="./jquery-1.4.2.js"></script>
  • <script type="text/javascript">
  •  
  • // I am the easing iteration funciton. This is built on top
  • // of the core animate function so that it can leverage the
  • // built-in timer optimization.
  • jQuery.ease = function( start, end, duration, easing, callback ){
  • // Create a jQuery collection containing the one element
  • // that we will be animating internally.
  • var easer = $( "<div>" );
  •  
  • // Keep track of the iterations.
  • var stepIndex = 0;
  •  
  • // Get the estimated number of steps - this is based on
  • // the fact that jQuery appears to use a 13ms timer step.
  • //
  • // NOTE: Since this is based on a timer, the number of
  • // steps is estimated and will vary depending on the
  • // processing power of the browser.
  • var estimatedSteps = Math.ceil( duration / 13 );
  •  
  • // Set the start index of the easer.
  • easer.css( "easingIndex", start );
  •  
  • // Animate the easing index to the final value. For each
  • // step of the animation, we are going to pass the
  • // current step value off to the callback.
  • easer.animate(
  • {
  • easingIndex: end
  • },
  • {
  • easing: easing,
  • duration: duration,
  • step: function( index ){
  • // Invoke the callback for each step.
  • callback(
  • index,
  • stepIndex++,
  • estimatedSteps,
  • start,
  • end
  • );
  • }
  • }
  • );
  • };
  •  
  •  
  • // -------------------------------------------------- //
  • // -------------------------------------------------- //
  •  
  •  
  • // Ease from star (1) to finish (100).
  • $.ease(
  • 1,
  • 100,
  • 1000,
  • "swing",
  • function(){
  • console.log( arguments );
  • }
  • );
  •  
  • </script>
  • </head>
  • <body>
  • <!-- Intentionally left blank. -->
  • </body>
  • </html>

As you can see, I am defining an iteration from 1 to 100 over the course of 1000ms using the "swing" easing logic that comes with the jQuery core library. When I execute this iteration, the following arguments are logged to the console:

[1.0002442725080185, 0, 77, 1, 100]
[1.0021984381069835, 1, 77, 1, 100]
[1.0549511893824843, 2, 77, 1, 100]
[1.3162401292595844, 3, 77, 1, 100]
[1.5386187677878915, 4, 77, 1, 100]
[1.9360193865549626, 5, 77, 1, 100]
[2.59404353372116, 6, 77, 1, 100]
[3.188245767482659, 7, 77, 1, 100]
[4.086498284786836, 8, 77, 1, 100]
[5.3855378065454635, 9, 77, 1, 100]
[6.047634999849525, 10, 77, 1, 100]
[6.899998311311322, 11, 77, 1, 100]
[7.735790848954654, 12, 77, 1, 100]
[8.789299598058602, 13, 77, 1, 100]
[9.823552772835283, 14, 77, 1, 100]
[11.103130839289712, 15, 77, 1, 100]
[12.359594482598432, 16, 77, 1, 100]
[13.78378224943241, 17, 77, 1, 100]
[15.169889687317145, 18, 77, 1, 100]
[17.30106396143236, 19, 77, 1, 100]
[19.067489633276345, 20, 77, 1, 100]
[20.903703338652814, 21, 77, 1, 100]
[22.805628223942726, 22, 77, 1, 100]
[24.769041540274877, 23, 77, 1, 100]
[26.789584019084327, 24, 77, 1, 100]
[28.583467563574366, 25, 77, 1, 100]
[30.556131432173697, 26, 77, 1, 100]
[32.4225127863664, 27, 77, 1, 100]
[34.17215696448678, 28, 77, 1, 100]
[35.79644171193677, 29, 77, 1, 100]
[37.43828402671404, 30, 77, 1, 100]
[39.247104825491, 31, 77, 1, 100]
[40.614355964536855, 32, 77, 1, 100]
[42.29606192000807, 33, 77, 1, 100]
[44.14175320508501, 34, 77, 1, 100]
[46.30629455396736, 35, 77, 1, 100]
[48.168230689872686, 36, 77, 1, 100]
[50.34449141944896, 37, 77, 1, 100]
[52.36566404216176, 38, 77, 1, 100]
[54.84863422926178, 39, 77, 1, 100]
[56.39530942469606, 40, 77, 1, 100]
[58.397061818725085, 41, 77, 1, 100]
[60.385644035463145, 42, 77, 1, 100]
[62.50866192437266, 43, 77, 1, 100]
[64.1606576911129, 44, 77, 1, 100]
[66.23934837470433, 45, 77, 1, 100]
[68.14238799630591, 46, 77, 1, 100]
[70.01600485497607, 47, 77, 1, 100]
[71.71667929686885, 48, 77, 1, 100]
[73.66252580589837, 49, 77, 1, 100]
[75.42934848097015, 50, 77, 1, 100]
[77.15459570091967, 51, 77, 1, 100]
[78.83539021693224, 52, 77, 1, 100]
[80.46892891535957, 53, 77, 1, 100]
[82.05248749256015, 54, 77, 1, 100]
[83.58342499830574, 55, 77, 1, 100]
[85.0591882401769, 56, 77, 1, 100]
[86.58394705735986, 57, 77, 1, 100]
[87.83544334643715, 58, 77, 1, 100]
[89.2283451235411, 59, 77, 1, 100]
[90.36274034270053, 60, 77, 1, 100]
[91.61449701019272, 61, 77, 1, 100]
[92.62422684883724, 62, 77, 1, 100]
[93.57409585614153, 63, 77, 1, 100]
[94.60482294732421, 64, 77, 1, 100]
[95.485584276624, 65, 77, 1, 100]
[96.23203685930869, 66, 77, 1, 100]
[96.96732595386678, 67, 77, 1, 100]
[97.67247740510233, 68, 77, 1, 100]
[98.245592213661, 69, 77, 1, 100]
[98.73908020293557, 70, 77, 1, 100]
[99.15211836755276, 71, 77, 1, 100]
[99.48401787083121, 72, 77, 1, 100]
[99.7342251935759, 73, 77, 1, 100]
[99.90232305719944, 74, 77, 1, 100]
[99.98803111963072, 75, 77, 1, 100]
[100, 76, 77, 1, 100]

Internally, the ease() function creates a detached DOM node and then uses jQuery's animate() method to "animate" a fake CSS property that will act as our iteration index. Then, we simply hook into the step-callback in order to invoke the given iteration callback, passing in our current iteration value.

It would have been simple enough to bypass the animate() method and set up my own timer using setInterval(). However, Javascript timers are expensive to use. jQuery handles this cost quite gracefully by creating a central timer from which all animate() methods are powered. As such, I figured it would make the most sense just to leverage the optimized infrastructure that jQuery already had in place.

Again, I am not sure why anyone would want to use this; but, it popped into my head last night and I wanted to see if I could make it happen. Of course, this implementation presupposed that one would want to use a duration over which the iteration would take place. We could remove the duration aspect altogether, which gives me a new idea... but more on that later.




Reader Comments

Actually, this turned out to be useful for me. I'm not sure why jQuery doesn't expose support to something like this by default.

Thanks!

Reply to this Comment

@Sathya,

Oh cool - glad you found it useful. I think the hardest thing about this, as far as generic usefulness, is that it requires the concept of a "duration" in addition to a start/end. Although, I suppose you could just think of this as the "step" portion of a traditional for-loop.

That said, I think it is cool and useful for specific things.

Reply to this Comment

Hi.

I stumbled upon your post on my search for a way to ease the changes on a temperature dial I'm trying to create for my Arduino weather station. I tried to figure it out on my own, but because I was using setTimeout and setInterval I couldn't incorporate a duration into the ewuation. So, first of all - THANK YOU :)

I have a small problem though - the easing always starts from 0, no matter what number I set as the start parameter.

I've set up a jsfiddle for testing, so maybe you can check it out and tell me what's wrong?

Here's the link: http://jsfiddle.net/witq/qtTKX/

Reply to this Comment

@Witek,

Aaaaand it's done. I changed the animated property from easingIndex to some existing css property - width. And it started working as expected.

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.