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

Posted September 9, 2010 at 10:08 AM by Ben Nadel

Tags: Javascript / DHTML

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

Sep 14, 2010 at 10:45 AM // reply »
11,238 Comments

As part of an exploration of function overloading, I have taken this concept and created two different forms of execution: with duration and without duration:

http://www.bennadel.com/blog/2010-An-Example-Of-Overloaded-Functions-With-Very-Different-Sub-Function-Implementations.htm


Mar 23, 2011 at 5:43 PM // reply »
1 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!


Mar 24, 2011 at 9:28 PM // reply »
11,238 Comments

@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.


Dec 15, 2012 at 5:40 AM // reply »
2 Comments

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/


Dec 15, 2012 at 5:52 AM // reply »
2 Comments

@Witek,

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


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 21, 2013 at 7:46 PM
Using Plupload For Drag & Drop File Uploads In ColdFusion
No luck. At least I have uncovered the cause, URLScan 3.1. Here is what I see in the IIS log when a file is over 30mb. 2013-05-21 23:29:05 10.105.45.128 GET /plupload/assets/jquery/jquery-1.8. ... read »
May 21, 2013 at 6:12 PM
Using Plupload For Drag & Drop File Uploads In ColdFusion
Ben, I did not see you after Pete Freitag's Lockdown session at cfObjective but he said that IIS sets file size limits at 30MB by default which just happened to be the threshold for file size when ... read »
May 21, 2013 at 11:51 AM
Ask Ben: Parsing Very Large XML Documents In ColdFusion
Looking at my first ever XML document that I have to parse and put into MS SQL 2000 with CF8. I get it to list the desired Field name, many times over, and have a long list of this field name displa ... read »
May 21, 2013 at 9:25 AM
Turning Off and On Identity Column in SQL Server
you are awesome..i am lucky to get this blog between such a garbage one....Thanks, Prashant ... read »
May 20, 2013 at 4:38 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, Your confusion is well founded, since this is a very confusing features. In fact, it ONLY works if you use array notation. Meaning, that this: arrayToList( query[ "columnName" ] ) ... read »
May 20, 2013 at 4:34 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I was thinking chicken and the egg, I wouldn't have expected it to work in the valuelist going in I guess. Maybe I just need a beer, long day :) ... read »
May 20, 2013 at 4:29 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
@Dana, That's if you're trying to reference a specific row. In this case, we're trying to reference the entire query column as one cohesive value. So, you are correct that if you wanted to output a ... read »
May 20, 2013 at 4:24 PM
Using A Dynamic Column Name With ValueList() In ColdFusion
I thought when you used array notation to reference queries you always had to have the row or it would throw a similar error as well? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools