I came across some code that was using a tangled web of setInterval ‘s and setTimeout ‘s to do… something. I wasn’t really sure what at first.
Here’s the code:
function animateSomeText() { setTimeout(function() { $('.animate-me').animate({ opacity: 1 }, 200, function() { setTimeout(function() { $('.animate-me').animate({ opacity: 0 }, 200); }, 1800); }); }, 3800); } setInterval(animatedText(), 6000);
After some investigating, I figured out two things — what the code was supposed to do, and what the code was actually doing.
Digging In
So what was the code supposed to do? Let’s find out with some comments.
function animateSomeText() { // We're setting a timeout for 3.8 seconds setTimeout(function() { // set opacity to 1 over .2 seconds, so 3.8 + .2 = 4 seconds total $('.animate-me').animate({ opacity: 1 }, 200, function() { // once that's done, set another timeout for 1.8 seconds setTimeout(function() { // animate again for .2 seconds, 1.8 + .2 = 2 $('.animate-me').animate({ opacity: 0 }, 200); }, 1800); }); }, 3800); } // and do that function every 6 seconds, which makes sense -- 4 seconds + 2 seconds from the function setInterval(animatedText(), 6000);
That all makes sense, right? Well, the problem is… it doesn’t. The animation runs once, and doesn’t repeat. But why?
At first I thought the issue was the logic. Surely something this hard to follow is missing something. But, reading over the code, it actually does do what it was meant to. It takes some text that’s set to opacity 0, fades it to 1, then after a few seconds fades it back to 0. But we see with the setInterval , this is supposed to happen every 6 seconds instead of just the one time.
As it turns out, the real problem is how the animatedText function is being called. Instead of a reference to a function being set as the first parameter in setInterval , the function call is set. This means that the function is run once (as expected), but on the next interval there is no reference to a function, so the interval has no idea what to do with it. So, to fix this snippet, all we really needed to do was change the last line to setInterval(animatedText, 6000); .
The Fix
This same issue seems to come up a lot, so in the spirit of helping out any future developers that come across this code in the wild, I though I would ditch the timeouts and intervals all together and write some code that is easier to read and understand, using jQuery’s delay() and queue() functions:
function animateSomeText() { $('.animate-me').animate({ opacity: 1 }, 200) // run the initial animation .delay(2000) // wait 2 seconds after the animation has finished .animate({ opacity: 0 }, 200) // animate back to opacity 0 .delay(4000) // wait 4 seconds after the second animation finishes .queue(function() { // access the queue of animations animatedText(); // put a new function at the top of the queue $(this).dequeue(); // pop the queue stack and run the function }); } animateSomeText(); // run the initial animation
With this, you only need to call animateSomeText once, and it will take care of itself. It’s also much easier to read and understand what the code is trying to do because we’re delaying for a set amount of time after each animation, instead of having to do millisecond math with out animations and timeout’s which should save us from a few headaches down the road if we need to alter the delays or change the animation speeds.