JS Tip of the Day: Animations With requestAnimationFrame

Animations With requestAnimationFrame
Level: Intermediate

CSS can provide a lot when it comes to adding animations to your website or web app. Whether through CSS Animations or CSS Transitions, it will generally be your preferred approach for adding motion to your content. But when it comes to those special cases where CSS just isn’t able cut it, you will have JavaScript to rely on.

When animating with JavaScript, you’ll be responsible for each frame of the animation. To best sync your animation frames with the rendering of the browser window, the Web API provides you with the function requestAnimationFrame(). requestAnimationFrame() accepts a callback that will run each time before the browser will render the screen. In this callback you would put the code needed to drive your animations. It receives one argument, a timestamp from when the page was first loaded.

function callback (timestamp) {
    // animate stuff
}
requestAnimationFrame(callback);

Unlike setInterval(), requestAnimationFrame() doesn’t continuously call your callback over and over again. It only calls it once for the next time there’s a render (making it closer to setTimeout()). If you want to keep animating over time, you need to keep requesting a new animation frame with your callback. You would do this by calling requestAnimationFrame() again inside the callback.

function callback (timestamp) {
    // animate stuff
    
    // keep animating
    requestAnimationFrame(callback);
}
requestAnimationFrame(callback);

If you want to stop animating, you can stop calling requestAnimationFrame(), or, much in the way setInterval() and setTimeout() can be stopped, you can capture the return value from requestAnimationFrame() and cancel it using cancelAnimationFrame().

function callback (timestamp) {
    // animate stuff
    
    // keep animating
    requestId = requestAnimationFrame(callback);
}
let requestId = requestAnimationFrame(callback);
cancelAnimationFrame(requestId); // cancels animation

The following is a more complete example, using an HTML “cat” element that follows the mouse using requestAnimationFrame(). It shows two ways of stopping the animation, one by not calling requestAnimationFrame() again if a certain condition is met, and the other by canceling the request id with cancelAnimationFrame() when the user clicks.

<span id="cat" style="position: absolute">cat</span>

<script>
let cat = document.getElementById('cat');
let catSpeed = 2;
let catPos = {x:0, y:0};
let mousePos = {x:200, y:200};

function updateCatPos () {
    let {x, y} = catPos;
    let dx = mousePos.x - x;
    let dy = mousePos.y - y;
    let isCaught = dx < catSpeed && dy < catSpeed;
    let angle = Math.atan2(dy, dx);
    x += catSpeed * Math.cos(angle);
    y += catSpeed * Math.sin(angle);
    cat.style.transform = `translate(${x}px, ${y}px)`;
    catPos = {x, y};
    return isCaught;
}

function updateMousePos (event) {
    mousePos = {x: event.clientX, y: event.clientY};
}
addEventListener('mousemove', updateMousePos);

function stopChase (event) {
    cancelAnimationFrame(chaseId);
}
addEventListener('click', stopChase);

function chaseLoop (timestamp) {
    let isCaught = updateCatPos();
    if (isCaught) {
        document.documentElement.style.cursor = 'none';
    } else {
        chaseId = requestAnimationFrame(chaseLoop);
    }
}
let chaseId = requestAnimationFrame(chaseLoop);
</script>

More info: