Cleaning up setinterval’s and settimeout’s in jQUery with delay() and queue()

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.

Leave a Reply

Your email address will not be published. Required fields are marked *