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 19, 2013 at 2:31 PM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
It's funny really just how well that image describes the way I would imagine most people that go with angular for some project is. I have had a similar roller-coaster ride with it as well, but not qu ... read »
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools