requestAnimationFrame in Javascript

Anton Ioffe - September 12th 2023 - 16 minutes read

In the relentless pursuit of creating dynamic, responsive, and performance-intensive web applications, developers often find themselves tangled between multiple tools and techniques. One such tool that subtly sits in the JavaScript toolkit is the requestAnimationFrame function. This article is formulated for senior-level developers who want to unleash full power of this method for writing performance-optimized code.

Brace yourselves while we take a deep dive into the depths of JavaScript's requestAnimationFrame. We will unpack its underlying concepts, pulling it apart, analyzing its potential, and stitching it back together with real-world code examples that will enhance your comprehension of its application.

Furthermore, we will conduct a detailed and anatomical study of its comparison with other timing interfaces, thereby helping you understand its performance implications. We'll also shed some light on the capabilities of JavaScript in creating animations, and the dynamic duo of CSS and JavaScript in the world of animations. By the end of the article, you will not only learn the intricacies and nuances of requestAnimationFrame, but you will also gain insights into its implementation in advanced frameworks like React. Ready partakers, let's roll!

Deciphering the requestAnimationFrame concept in JavaScript

RequestAnimationFrame is a JavaScript method that signals the browser to perform an animation and requests that the browser schedule a repaint of the window for the next animation frame.

Primarily, requestAnimationFrame() is used for creating smooth browser animations. It essentially tells the browser, "Next time you’re about to re-draw, call this function" and hands off an animation function to the browser.

Now, let's delve into key aspects associated with this concept — purpose, usage, and execution.

Purpose of requestAnimationFrame

The main purpose of requestAnimationFrame() is to achieve performant, smooth, and reliable animations in JavaScript. The method allows the browser to optimize the animation, which may lead to better consistency, battery efficiency, and overall performance compared to traditional setInterval or setTimeout based animations.

Usage of requestAnimationFrame

The basic usage of requestAnimationFrame() is straightforward. Let's consider the following code example:

function animate() {
    // Animation code here

    requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

In this example, we first define an animation function. Inside this function, we call requestAnimationFrame, which then invokes the original animation function on next visual update.

Execution of requestAnimationFrame

The requestAnimationFrame() method takes a single argument, a callback function, which is invoked before the next repaint. Let's take a look at how requestAnimationFrame() is executed in a code context:

function animate() {
    const element = document.getElementById('element-to-animate');

    // Modify element properties for animation effect
    element.style.left = parseInt(element.style.left, 10) + 5 + 'px';

    requestAnimationFrame(animate);
}

requestAnimationFrame(animate);

In this example, the function animate() moves an HTML element 5 pixels to the right, each time it's called by requestAnimationFrame(). The animation will continue indefinitely because animate() is recursively calling requestAnimationFrame().

A common mistake when using requestAnimationFrame() is to not use a polyfill for browser compatibility. This can cause animations to not work as expected in older browsers. Here's how you can tackle it:

Common Mistake: Not using a polyfill for browser compatibility.

// Animation might not work in all browsers
requestAnimationFrame(animate);

Correct Way: Use a polyfill for browser compatibility.

// Polyfill
window.requestAnimationFrame = window.requestAnimationFrame 
                            || window.mozRequestAnimationFrame 
                            || window.webkitRequestAnimationFrame 
                            || window.msRequestAnimationFrame 
                            || function(f){return setTimeout(f, 1000/60)} // fall back to setTimeout

// Animation should work in all browsers
requestAnimationFrame(animate);

To conclude, requestAnimationFrame() is an efficient way to create smooth and efficient browser animations. But remember to use a polyfill for maximum browser compatibility. It provokes you to think: Are there more effective ways to maintain compatibility without risking the reliability of your animations? How can we make the above code more concise and modular for reusability in other animation contexts?

A Side-by-Side Analysis: requestAnimationFrame versus Other Timing Interfaces

In the realm of browser-run animations and timers, a variety of methods are available. Yet with each method offering its unique approach, it becomes essential to clarify the distinctions, benefits, and limitations of each.

This section meticulously explores the differences between requestAnimationFrame(), setTimeout(), and requestVideoFrameCallback(), three of the most commonly employed timing interfaces in contemporary coding.

Contrasts Among the Timing Interfaces

requestAnimationFrame

The main aim of requestAnimationFrame() lies in enabling smooth animations. It aligns with the refresh rate of the client's device, thus allowing for up to 60 display updates per second, more noticeable in standard devices.

function animate() {
    // Animation code
    requestAnimationFrame(animate);
}
animate();

Despite its utility, requestAnimationFrame() has its drawbacks. For instance, it can be complex to calculate animations requiring a specific duration or easing. To illustrate, consider you want the animation to span over 2 seconds. In this case, you would need to calculate the percentage of elapsed time for easing functions, making the entire process more tedious.

setTimeout

On an elementary level, setTimeout() facilitates delaying code executions or running them in intervals.

let countdown;
function timer(seconds) {
    clearInterval(countdown);
    countdown = setInterval(() => {
        seconds--;
    }, 1000);
}
timer(10);

However, if leveraged for creating frame rates, setTimeout() could potentially lead to choppy animations. For example, in slower systems, it might not keep up with the frame rate thus causing jumpy, unsmooth animations.

requestVideoFrameCallback

A newer entrant to the scene, requestVideoFrameCallback() is primarily used for video frame manipulation.

function frameUpdate(now, metadata) {
    // Video frame handling
    requestVideoFrameCallback(frameUpdate);
}
requestVideoFrameCallback(frameUpdate);

Even so, requestVideoFrameCallback() is not fully supported by older browsers and devices, which somewhat limits its use.

Conclusion

From the above, we can conclude that while all methods have their uses, requestAnimationFrame() usually provides superior results for animations. Despite setTimeout() being straightforward to implement, it can compromise the quality of complex, high-performance animations. On the other hand, requestVideoFrameCallback() turns out to be effective for handling video frames but has restrained use outside of this niche.

Common Mistake

A basic mistake developers often make is the indiscriminate use of methods without considering what exactly they are best suited for. For instance, let's consider a scenario where setTimeout() is used for animation. Incorrectly using setTimeout() instead of requestAnimationFrame() for animations may lead to choppy visuals and poor performance.

let position = 0;
function animate() {
     position += 10;
     element.style.left = position + 'px';
     if (position < 100) {
          setTimeout(animate, 1000/60); 
     }
}
animate();

And here's its rectified variant:

let position = 0;
function animate(timestamp) {
     position += 10;
     element.style.left = position + 'px';
     if (position < 100) {
          requestAnimationFrame(animate); 
     }
}
requestAnimationFrame(animate);

In the incorrect example, setTimeout() might not always match the display's refresh rate, possibly leading to choppy animations. In contrast, the corrected version uses requestAnimationFrame(), providing a smoother, more reliable animation.

It is essential to understand your project requirements before choosing your methods. Long animations with significant changes in states like sliders, loaders, and transitions are superior options for requestAnimationFrame(). However, lighter tasks not entailing visual cues, such as delaying API calls or intervals for time-based actions, could be handled better with setTimeout().

In the grand scheme of things, the animation and timing interfaces of JavaScript play a crucial role. Understanding their specific nuances can dramatically enhance the experience for users and the performance of applications.

In light of the above, here's a question to ponder: what potential real-world scenarios might arise where altering your choice of timing method could dramatically improve the performance or user experience of your project?

Animation Essentials: JavaScript and Animation Capabilities

JavaScript's Flexibility in Animations

JavaScript provides numerous options for creating animations. One of the most fundamental approaches is working directly with the Document Object Model (DOM), interacting with HTML elements on the page to create interactive and dynamic effects.

Consider a simple animation where you want to move an image across the screen. We could do this with pure JavaScript by manipulating our image's CSS styles.

let img = document.getElementById('myImg');
let posX = 0; 
setInterval(() => {
    posX += 5; // moving speed
    img.style.left = posX + 'px';
}, 20); // update every 20ms 

In this case, setInterval creates a continuous movement effect by repositioning the image every 20 milliseconds. Note that we're adjusting the left CSS property, causing the image to traverse horizontally. This setup is simplistic and not entirely reliable, as it doesn't account for differences in frame rates in different browsers or devices.

Animate and requestAnimationFrame

The built-in animate method and requestAnimationFrame are two contemporary approaches to JavaScript-based animations, each with its own practical applications and considerations.

The animate method belongs to the Element interface in the Web API. It allows the definition of keyframes and timing properties for animations, and yields a promise when the animation concludes. An example of a simple fade-in effect could be:

let elem = document.getElementById('myElement');
elem.animate([
    // keyframes
    { opacity: '0' },
    { opacity: '1' }
  ], { 
    // timing options
    duration: 1000,
});

One critical issue with the animate method is that it is not fully supported in all browsers, leaving your animations nonoperational in some cases.

Conversely, requestAnimationFrame does not suffer from the same compatibility issues. It allows for more precise control and better performance because it hooks into the browser's rendering engine. This function should be called for every frame in your animation, recursively:

function animate(time) {
    // Update animation properties...
    requestAnimationFrame(animate);
}
requestAnimationFrame(animate);

Comparison & Decisions

While requestAnimationFrame is optimized for creating smooth animations and is more widely supported, it requires more code to produce elaborate effects compared to animate. In contrast, the animate method offers a more compact and easier-to-read syntax but lacks universal browser support.

When choosing between these functionalities, consider the complexity of the animation, the audiences' tech stack, and how crucial the animation is for the user experience.

Mistake: Assuming setInterval or setTimeout will create a smooth animation.

Correction: Due to factors outside your control, such as processor load and display refresh rate, these methods can lead to jerky animations. requestAnimationFrame mitigates this problem by automatically adjusting to the ideal frame rate.

Mistake: Misunderstanding the semantic difference between animate and requestAnimationFrame.

Correction: animate is a method that applies directly to HTML elements and is great for quick or one-shot, simpler animations. requestAnimationFrame is a method of the global window object that needs to be called for every frame, making it a good fit for continuous, complex animations.

Ultimately, JavaScript provides flexibility and control for creating animations. Future developers would benefit from a firm understanding of these animation capabilities. After all, a well-placed animation can enrich a user interface and provide a stimulating user experience. Varying techniques depending on context, applying them appropriately, and avoiding common mistakes will enable a more efficient and effective animation development process.

CSS vs JavaScript Animations: A Comparative Study

While designing animations for the web, we are often met with a dilemma that can potentially impact how our end product behaves across different environments - should we choose CSS or JavaScript to power these animations? This section breaks down the comparison between CSS and JavaScript animations, discussing the ideal contexts for each, along with their pros and cons.

CSS Animations

CSS animations rely heavily on the browser's native rendering engine, which provides a significant benefit – they tend to be hardware accelerated. As a result, they normally outperform JavaScript-driven animations. Below is an example of a CSS animation:

.container {
    width: 100px;
    animation: size 5s infinite;
}

@-webkit-keyframes size {
    0% { width: 100px; }
    50% { width: 200px; }
    100% { width: 100px; }
}

@keyframes size {
    0% { width: 100px; }
    50% { width: 200px; }
    100% { width: 100px; }
}

This code continuously animates a .container div, causing it to oscillate in size.

Pros of CSS Animations

  1. Performance: Owing to their hardware acceleration capabilities, CSS animations are typically more performant.
  2. Simplicity: The CSS syntax is less intricate than JavaScript, making it more straightforward, especially when it comes to creating simple animations.

Cons of CSS Animations

  1. Limitations: CSS falls short when dealing with complex animations – a problem that can be resolved with JavaScript.
  2. Control: CSS offers less control compared to JavaScript animations, wherein you can pause, rewind, and more.

JavaScript Animations

While JavaScript can be more resource-intensive, it offers a higher level of flexibility and control, most notably for complex animations.

Here, we have a highly optimized instance of a JavaScript animation, leveraging the requestAnimationFrame() function:

// Retrieve the target element by its id
let elem = document.getElementById('myElem');

let pos = 0;

// Comment comes before the line of code
let animationId; // Initialize the variable for the animation frame id

// Set up animation using requestAnimationFrame
function frame() {
    if (pos === 350) {
        cancelAnimationFrame(animationId);
    } else {
        pos++;
        elem.style.left = `${pos}px`;
        animationId = requestAnimationFrame(frame);
    }
}

animationId = requestAnimationFrame(frame);

This script meticulously and optimally moves an element (with the id 'myElem') horizontally across its parent container while staying harmoniously in sync with the browser's refresh rate.

Pros of JavaScript Animations

  1. Flexibility: JavaScript excels with its large variety of tools and greater control, making it markedly more flexible than its CSS counterpart.
  2. Complexity: JavaScript efficaciously handles complex, multi-step animations.
  3. Synchronization: Thanks to requestAnimationFrame(), animations can adjust to the refresh rate of a wide range of devices and browsers, ensuring a smoother and more synchronized performance.

Cons of JavaScript Animations

  1. Performance: Unless it is carefully optimized, JavaScript animations can risk becoming slower, especially if they circumvent hardware acceleration.
  2. Complexity: For smaller animations that CSS could handle smoothly, using JavaScript could introduce unnecessary complexity.

A common pitfall to watch out for:

let id = setInterval(frame, 0);

In JavaScript, assigning the interval value to 0 doesn’t necessarily make the animation faster. This zero value is both unrealistic and undesirable as it can incessantly load up the browser's event queue, causing undue strain and blocking other vital scripts from running. JavaScript has a minimum interval of around 4ms to 5ms. Anything less will default to this boundary for protection.

The recommended route would be:

let id = setInterval(frame, 4);

In summary, when it comes to performance and simplicity, CSS animations hold the upper hand, especially for simpler animations. On the other hand, JavaScript rises to the top when advanced level control is needed for complex animations or interactive sequences. In the end, the choice between CSS and JavaScript for crafting your animations will primarily hinge on the type and complexity of the animations you need, alongside your comfort and familiarly with each methodology.

Key Takeaways:

  • CSS animations are simple and efficient for light and simple animations.
  • JavaScript offers more control and is better suited for complex animations.
  • The requestAnimationFrame() function in JavaScript is perfectly in sync with the browser's refresh rate, making animations smoother and better aligned across a variety of devices.

Thought-Provoking Question: How would you leverage the strengths of both CSS and JavaScript to optimize an animation-heavy web project?

Implementing requestAnimationFrame in Advanced JavaScript Frameworks

Implementing requestAnimationFrame in modern JavaScript frameworks like React presents a set of unique challenges and opportunities.

One common issue developers encounter when working with requestAnimationFrame in React involves delays or requestAnimationFrame not functioning as anticipated. This usually occurs when trying to implement animations using React's Virtual DOM paradigm.

To understand this better, let's take a look at a sub-optimal code example. Here is an attempt to implement requestAnimationFrame:

class AnimationComponent extends React.Component {
    componentDidMount() {
        this.animationFrame = requestAnimationFrame(this.animateSomething);
    }

    animateSomething() {
        // Animation logic here
        this.animationFrame = requestAnimationFrame(this.animateSomething);
    }

    componentWillUnmount() {
        cancelAnimationFrame(this.animationFrame);
    }
}

In the above code, an animation frame is set in componentDidMount(), then the idea is to continuously re-render our animation in animateSomething(). However, this might not operate as expected.

Why is this so? The reason lies in the nature of React's lifecycle methods. The componentDidMount() method fires immediately after a component is added to the DOM, and at that point, all child components may not be rendered. As a result, our animation could either fail to run or cause unexpected behaviours.

A practical solution to this problem is to make sure all components are mounted and rendered before we request the animation frame. This can be achieved by using window.setTimeout() with a delay of zero. Here is the correct way to implement this:

class AnimationComponent extends React.Component {
    componentDidMount() {
        // Sets a timer with a delay of zero to ensure all components are mounted
        window.setTimeout(() => {
            // Requests an animation frame only after all components are mounted
            this.animationFrame = requestAnimationFrame(this.animateSomething);
        }, 0);
    }

    animateSomething() {
        // Animation logic here
        // Requests the next animation frame
        this.animationFrame = requestAnimationFrame(this.animateSomething);
    }

    componentWillUnmount() {
        // If the component gets unmounted, the animation frame loop is stopped 
        cancelAnimationFrame(this.animationFrame);
    }
}

By using setTimeOut() with a zero delay, we ensure that requestAnimationFrame() is called only after all child components are mounted. This guarantees all required components are ready for the requestAnimationFrame callback.

Another key point to bear in mind is to invariably halt the requestAnimationFrame loop when our React component un-mounts. Neglecting to do this could make your app try to run animations for components that no longer exist. This can lead to errors being thrown by React or even memory leaks. As seen in the corrected code example, cancelAnimationFrame() ceases the loop when the component is removed from DOM.

A common coding mistake associated with requestAnimationFrame revolves around the use of the this keyword. Here's an example:

class AnimationComponent extends React.Component {
    componentDidMount() {
        this.animationFrame = requestAnimationFrame(this.animateSomething);
    }

    animateSomething() {
        // Animation logic here
        this.animationFrame = requestAnimationFrame(this.animateSomething);
    }

    componentWillUnmount() {
        cancelAnimationFrame(this.animationFrame);
    }
}

In this instance, the this inside animateSomething() refers to the global object, meaning this.animationFrame is undefined, and the animation fails to run. The correct code should include .bind(this) in requestAnimationFrame(this.animateSomething.bind(this)) to ensure this within animateSomething() corresponds to the correct object:

class AnimationComponent extends React.Component {
    componentDidMount() {
        this.animationFrame = requestAnimationFrame(this.animateSomething.bind(this));
    }

    animateSomething() {
        // Animation logic here
        this.animationFrame = requestAnimationFrame(this.animateSomething.bind(this));
    }

    componentWillUnmount() {
        cancelAnimationFrame(this.animationFrame);
    }
}

As this example demonstrates, always ensure you're aware of the context your functions are executing in when using the this keyword in JavaScript.

When working with requestAnimationFrame in React or any other advanced JavaScript framework, remember that understanding and properly employing the framework's lifecycle methods is crucial.

It's equally vital to monitor your animations and stop them when they're not needed anymore. Not only will this save essential resources, but it will also ensure your application remains performant and free from bugs.

Now considering your usage of requestAnimationFrame in advanced JavaScript frameworks like React, how do you manage potential timing issues or animation delays? What specific approaches or strategies do you employ to ensure that animations in your applications run smoothly and efficiently?

Summary

In this article, we deep-dive into the "requestAnimationFrame" function, a hidden gem in the JavaScript toolkit that can be key to creating performant animations for dynamic web applications. We pull apart this function's underlying concepts, compare it with other timing interfaces, and provide code examples of its usage. The article further illuminates the animation capabilities of JavaScript, contrasts these with CSS, and discusses challenges of integrating "requestAnimationFrame" into advanced frameworks such as React.

Key takeaways include recognizing "requestAnimationFrame" as an efficient tool for performance-optimized animations, understanding the differences between "requestAnimationFrame", "setTimeout", and "requestVideoFrameCallback", and seeing the importance of using polyfills to ensure maximum browser compatibility. The peculiarities of implementing "requestAnimationFrame" in React and utilizing it in synchronizing with the browser's refresh rate for smoother performance were also clarified.

Put your newfound knowledge to the test in this technical challenge: Implement an animation sequence in React using "requestAnimationFrame", ensuring that all child components required for the animation are properly mounted prior to animation frame requesting. Consider potential timing issues and develop a strategy to precisely manage and control animation delays. Test your solution across multiple browsers and optimize your code by including a polyfill.

Don't Get Left Behind:
The Top 5 Career-Ending Mistakes Software Developers Make
FREE Cheat Sheet for Software Developers