Using jQuery's animate() Method To Power Easing-Based Iteration
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.
Want to use code from this post? Check out the license.
Reader 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:
www.bennadel.com/blog/2010-An-Example-Of-Overloaded-Functions-With-Very-Different-Sub-Function-Implementations.htm
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!
@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.
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/
@Witek,
Aaaaand it's done. I changed the animated property from easingIndex to some existing css property - width. And it started working as expected.
@Witek,
Thanks! That was just what I needed.
@Ben,
I used this to animate a range input at page load. Thanks!