What’s the best way to do animations with JavaScript?

What’s the best way to do animations with JavaScript?

A web designer uses web animations, which are moving graphics, to draw in visitors and frequently persuade them to complete specific activities. They can be brief animations that draw attention to a particular piece, deliver a commercial message, or facilitate more natural user navigation. Animations, when utilized well, may bring your website to life and keep users’ interest.

In this article, we’ll describe several ways of doing animations with JavaScript and find what’s best.

Different ways to Animate

Use CSS when UI components have more compact, independent states. For pulling a navigation menu in from the side or displaying a tooltip, CSS Transitions and CSS animations are perfect.

Use JavaScript when you want extensive control over your animations. The standards-based method is the Web Animations API, which is present in the majority of contemporary browsers. Real objects are provided, which are perfect for sophisticated object-oriented systems. JavaScript can also be helpful when you need to halt, pause, slow down, or reverse your animations.

If you want to manually create a whole scene, use requestAnimationFrame. This is a comparatively complex JavaScript technique, but it might be helpful if you're making games or want to run animations in loops.

Use CSS animations for one-shot transitions like toggling UI element states. Animations on HTML elements via CSS can be achieved using either CSS Animations or CSS Transitions.

CSS Transitions

With CSS transitions, you may gradually modify an element’s property values over a specified period. All we need to describe are two things to create a transition effect. One is the CSS property to which the effect will be added, and the other is the time length of the effect because if you omit the time duration, the default value will be 0 seconds, and no transition will occur.

Example:


div {
width: 80px;
height: 80px;
background: blue;
transition: width 3s;
}

div:hover {
width: 1500px;
}


CSS Transition
Move the mouse over the box below to see the transition

Output: A blue box with dimensions of 80px width and height will extend up to 1500px every 3 seconds whenever it is being hovered.

CSS Animations

By altering the element’s attributes, a CSS animation enables an element to progressively switch from one style to another. We are free to alter such characteristics as often as we choose. CSS animations comprise two parts: a style that describes the animation and a collection of keyframes that show the style’s beginning and ending states as well as any potential middle points.

Rule of @keyframes

The styles that an element will have at specific moments are stored in keyframes. The @keyframes rule allows us to provide all of the styles, allowing us to specify which styles (element attributes) an element should have at what stage of animation. Additionally, we need to link an animation to an element for it to function.

Note: The animation-duration parameter specifies how long an animation should last. No animation will take place if the animation-duration attribute is left blank because its default value is 0s (0 seconds).

Example:


div {
width: 100px;
height: 100px;
background-color: blue;
position: relative;
animation-name: example;
animation-duration: 8s;
}

@keyframes example {
0% {
background-color: pink;
left: 0px;
top: 0px;
}

25% {
background-color: purple;
left: 200px;
top: 0px;
}

50% {
background-color: yellow;
left: 200px;
top: 200px;
}

75% {
background-color: orange;
left: 0px;
top: 200px;
}

100% {
background-color: red;
left: 0px;
top: 0px;
}
}


CSS Animation


Note:After finishing, animation goes back to its original style.

Output: A box with 100px height and width will change its colors and position while going from 0% to 100% at different stages (25%, 50%, 75%, 100%) as specified by the % parameter in the code above.

Compared to implementing CSS transitions or animations, writing JavaScript animations is more difficult, but it often gives developers a lot more control. JavaScript is advised if you need to link more than three animations together. When there are delays, CSS animation sequencing gets complicated, and if the timing is off, you’ll have to perform several recalculations. JavaScript makes the writing and reading of lengthy, intricate, sequential animations simpler.

In JavaScript, only two functions, setTimeout and setInterval, provide asynchronous execution. This implies that code can be performed without waiting for the current execution to finish; instead, it can start running right away.

setInterval()

The setInterval method in JavaScript repeats a specified function at each interval of time. It is a technique for animating a canvas at 60 frames per second. It calls another function at specified intervals of time and then returns a unique id of the performed action.

Syntax:

setInterval(function, ms);

Parameter: There are two parameters in this method :

  • function: first parameter to be executed
  • ms: indicates the length of the time interval between each execution.

Example:


var myVar = setInterval(myTimer, 1200);
function myTimer(){(
document.getElementById("OPN").innerHTML += "

Open Replay

";
)}

Output: After every 1.2 seconds, a new “Open Replay” message will be displayed.

As the setInterval() method executes the function infinitely, there is a method called clearInterval() to stop the execution of the setInterval().

Syntax:

window.clearInterval(value);

Parameter:

  • value: The function to be stopped

Example: In this example, we will first execute a setInterval() function and then stop its execution by using the clearInterval() function.


Stop Execution
var myVar = setInterval(myTimer, 1200); function myTimer()
{(document.getElementById("OPN").innerHTML += "

Open Replay

")}{" "}

Output: The ‘Open Replay’ message will render on the screen until the user presses the ‘Stop Execution’ button.

If you run many animations simultaneously, using setInterval() will make debugging difficult. The fact that setInterval() will play the animation every 'n' milliseconds, regardless of how long it takes the function to run, is another issue. To put it another way, setInterval() may queue too many function executions if the function takes too long to perform.

setTimeout()

This method executes a function after waiting a specified number of milliseconds. This method executes the code only once.

Syntax:

window.setTimeout(function, milliseconds);

Parameter: There are two parameters in this method:

  • function: first parameter to be executed
  • milliseconds: the number of milliseconds before the execution takes place.

Example:


Press me
function opn()
{alert("Welcome 2023")}{" "}

Output: As soon as the user presses the ‘Press Me’ button, then after a pause of 1.5 seconds the message alert box ‘Welcome 2023’ will pop up.

The execution of the setTimeout() function is stopped by using a method called as clearTimeout().

Syntax:

window.clearTimeout(value);

Parameter:

The clearTimeout() method is only used if the function has not been executed. Let us see an example below.

Example: We will use a setTimeout() function and stop its execution by using clearTimeout() function before the execution of the setTimeout().


Press Stop Button before the Alert pops up

Press me

Stop Execution
function opn()
{alert("Welcome 2023")}


Output: If we click the STOP button before the alert is shown, the execution of the alert is stopped.

The drawback of using setTimeout() is that it does not wait for the timeout period to expire before allowing the remaining JavaScript code to run. In the majority of cases when the function will take a bit longer to perform, this will produce erroneous results.

Traditionally, you could have powered your animation loop using a function like setInterval or setTimeOut. These two functions have a straightforward issue. They are unaware of everything happening on the rest of the page apart from the animation. This makes them incredibly ineffective at powering animations since they frequently require a repaint or update that your browser isn't prepared to perform.

setTimeout or setInterval don't consider any other browser activity. The animation may have scrolled off the page, eliminating the need for a second update request, or the page may be hidden behind a tab, using up needless CPU resources. Secondly, rather than updating the screen whenever the computer can, setTimeout only does so when it wants to. That means your poor browser must balance redrawing the animation with redrawing the entire screen. If your animation frame rate is out of sync with the redrawing of your screen, it may consume extra processing power. This results in increased CPU consumption or a depleted battery on your mobile device. These efficiency issues are solved using the requestAnimationFrame.

Session Replay for Developers

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — an open-source session replay suite for developers. It can be self-hosted in minutes, giving you complete control over your customer data

Happy debugging! Try using OpenReplay today.

requestAnimationFrame()

The requestAnimationFrame function was developed by Mozilla (the company behind Firefox) and eventually adopted and refined by the WebKit team. It offers a native API for displaying animations of any kind in the browser, whether they are made with DOM components, CSS, WebGL, or another technology.

Here’s how you use it:

function draw() {
requestAnimationFrame(draw);
} // Code for drawing goes here
draw();

It is identical to the version with setTimeout, except requestAnimationFrame is used in its place. You can optionally supply an argument to the function that is being called, such as the element that is currently being animated, like in:

element, requestAnimationFrame;

However, you might have observed that you don’t mention an interval rate. How frequently is the draw function used then? It all depends on the frame rate of your browser and computer, but generally speaking, it’s 60 frames per second (fps). The main distinction is that, instead of specifying an interval, you are asking the browser to render your animation when a suitable chance arises. Additionally, it has been suggested that depending on the load, element visibility (being scrolled out of view), and battery level, browsers may decide to improve the performance of the requestAnimationFrame.

The requestAnimationFrame's ability to compile all of your animations into a single browser repaint is another benefit. By doing this, CPU cycles are saved, and your gadget has a longer, happier life. Therefore, all of your animations should become silky smooth, synchronized with your GPU, and consume far less CPU if you utilize requestAnimationFrame. Additionally, if you switch to a new tab, the browser will slow the animation down so it won't take over your computer while you're using it.

Syntax:

requestAnimationFrame ();

RequestAnimationFrame tells the browser to run the animation and ask for an updated animation before the next repaint. It executes a callback after the browser paints the screen, as opposed to setTimeout, which does it after the specified time delay. Since requestAnimationFrame will wait until the browser is prepared to paint rather than execute the function even if the browser is not, this alone makes it more efficient than setInterval.

The requestAnimationFrame method in JavaScript simply takes one argument: a function to run. The operation of the function you gave to requestAnimationFrame will start when the browser is ready to redraw the screen. The CPU power of the machine running the code and the refresh rate of the display the browser is on will affect when this function executes.

Example :


requestAnimationFrame Demo

Start
Stop



.circle {
border - radius: 100 %;
width: 200 px;
height: 200 px;
background - color: purple;
align - items: center;
justify - content: center;
font - family: Arial;
position: relative;
}

function easeInCubic(t) {
return t ** 3;
}
const circle = document.querySelector(".circle");
const startButton = document.querySelector(".startanimation");
const stopButton = document.querySelector(".stopanimation");
let animationId = null;

function animate(element, duration) {
window.cancelAnimationFrame(animationId);
let start = null;

function step(timestamp) {
if (!start) {
start = timestamp;
}
const progress = timestamp - start;
const translation = 1000 * easeInCubic(progress / duration);
console.log(progress, translation);
//element.style.transform = `translateX(${translation}px)`;
element.style.left = translation + "px";
if (progress < duration) {
animationId = window.requestAnimationFrame(step);
}
}
animationId = window.requestAnimationFrame(step);
}
startButton.addEventListener("click", () => animate(circle, 2000));
stopButton.addEventListener("click", () => {
window.cancelAnimationFrame(animationId);
});

Output:

A purple color ball will go from left to right on pressing the ‘Start’ button for a duration of 2000ms(2 seconds). After the user clicks on the ‘Stop’ button, the ball will stop immediately.

Just before the animation runs, we set the startime variable to the current time using requestAnimationFrame's timestamp. Its value is automatically passed in as the first parameter of the callback function of requestAnimationFrame that contains a highly accurate representation of the current time in milliseconds (accurate to 5 microseconds). This informs us when the animation started running.

Inside the animation function step(timestamp), we capture the current time of the current "frame" using the variable timestamp. We use the difference between that and the animation start time to figure out at what "point" along the animation we're currently at.

We stop the ball using window.cancelAnimationFrame(animationId); as explained below in detail.

Cancelling requestAnimationFrame()

Syntax:

window.cancelAnimationFrame(animationId);

Similar to setInterval/ setTimeout, one can cancel a requestAnimationFrame call. RequestAnimationFrame when called returns a non-0 integer that can be captured inside a variable and passed into its counterpart cancelAnimationFrame() to stop it from being invoked again.

Within the function animate(element,duration), window.cancelrequestanimationframe(); is called. So the animation will stop either when the duration of 2000ms is reached or when the user presses the 'STOP' button whichever is done first. The above code logs the timestamp parameter value of requestAnimationFrame for 2 seconds(2000ms), using cancelAnimationFrame to stop the former.

Advantage of requestAnimationFrame

The following are the advantages of using requestAnimationFrame :

Animations Only Run When Visible

When you use requestAnimationFrame, your animations only play when the user can see the tab (or window). This results in decreased use of the CPU, GPU, and memory. RequestAnimationFrame uses CPU efficiency by avoiding rendering while a tab or window is hidden. RequestAnimationFrame will cease executing animation operations if the focused browser tab changes to avoid wasting resources.

Browser Optimization

When you use requestAnimationFrame, the browser enhances your animations to make them smoother by using fewer resources. It groups several animations into a single reflow and repaint cycle and does away with the potential of pointless draws.

Battery Friendly

By reducing the number of “tasks” a device needs to perform to produce your animations, the optimizations outlined in the previous two paragraphs all contribute to battery power conservation. This is crucial for mobile devices since batteries normally last just a short time.

Since most of the libraries we’ll be working with utilize it internally, requestAnimationFrame is a fairly high-performance approach to animating. Similar to recursion, requestAnimationFrame performs the logic before drawing the next frame in a series and then calls itself once more to continue.

Drawbacks of requestAnimationFrame

Despite its advantages over other methods of animation, requestAnimationFrame has few drawbacks.

Browser Support

Since this is a new API, it is only now accessible in browsers through a vendor prefix, such as webkitRequestAnimationFrame in Chrome, Safari, and Firefox. Overall, browser support is decent; however, Microsoft only supports msRequestAnimationFrame in IE10 and later.

See the ‘Can I Use’ tables to check the browser support for requestAnimationFrame. The only problems are iOS 5-, IE 9- and Android. But these can be fixed using the following:

Polyfill

A polyill has been created by Paul Irish ( Google), Eric Möller ( Opera) and Tino Zijdel ( Tweakers.net).Using this, one can use requestAnimationFrame in literally any browser.You can drop this in your code and can use requestAnimationFrame as desired.

(function () {
var lastTime = 0;
var vendors = ["ms", "moz", "webkit", "o"];
for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x] + "RequestAnimationFrame"];
window.cancelAnimationFrame =
window[vendors[x] + "CancelAnimationFrame"] ||
window[vendors[x] + "CancelRequestAnimationFrame"];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function (callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function () {
callback(currTime + timeToCall);
}, timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function (id) {
clearTimeout(id);
};
})();

Variation in Animation Speed

RequestAnimationFrame allows us to generate animations, but not all users will experience them at the same speed. The reason is that the timing of the browser's decision to do a repaint has a significant impact on how quickly requestAnimationFrame is called. The device's screen refresh rate is a significant element in whether a repaint occurs, among many others.

An animation’s running speed varies wildly depending on whether one is viewing the animation on a 60Hz screen (screen tries to redraw 60 times per second) or on a 120Hz screen (screen tries to redraw 120 times per second). This variation isn’t desirable, especially when we use requestAnimationFrame to power our game loops and other visually-intense animations whose speed we want to keep consistent.

Let’s take a closer look at why requestAnimationFrame performs differently depending on the user. Unquestionably an important reason is the refresh rate of the screen. While many devices only have a 60Hz refresh rate, more of us are using 75Hz, 90Hz, 120Hz, 240Hz, or even greater refresh rates on devices like higher quality laptops, desktops with gaming displays, newer mobile phones, etc.

The speed at which an animation loop driven by requestAnimationFrame operates depends on several factors in addition to the refresh rates. The repaint is given less attention since the browser is too busy with something more important. The browser is shrunk, or the attention is lost on the browser tab. The gadget slows down when optimizing for battery and CPU. High refresh rates are not supported by the browser(eg, Safari).

We must consider these and devise a method to guarantee that everyone views our visual updates and animations at the same rate. Animations that play at the same speed for every user can be made.

Setting Frame Rate to a Consistent Value

It’s crucial to keep the frames per second limit under control, especially when creating games with animated elements that must not go above a certain threshold. The requestAnimationFrame() method is not a timer or a loop; instead, it is used to simply redraw the screen.

RequestAnimationFrame is a good choice for frame rate management since each time a function call is made, the screen or frame is repainted in accordance with the developer's update code.

There are two ways to set the frame rate to a consistent value. They are as below :

Using setTimeout() method

The setTimeout() method is used to regulate frame rate, while the requestAnimationFrame() function is used to update the screen at the defined intervals.

The following code demonstrates how to manage the frame rate using setTimeout(). To keep the screen refreshing at the desired frame rate, requestAnimationFrame() is sent to setTimeout() as a function.

var frameCount = 0;
var $results = $("#results");
var fps, fpsInterval, startTime, now, then, elapsed;
startAnimating(5);

function startAnimating(fps) {
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
console.log(startTime);
animate();
}

function animate() {
setTimeout(function () {
// requestAnimationFrame() is called
// with animate() callback
// to update content at specified fps
requestAnimationFrame(animate);
// ... Code for Animating the Objects ...
now = Date.now();
var sinceStart = now - startTime;
var currentFps =
Math.round((1000 / (sinceStart / ++frameCount)) * 100) / 100;
$results.text(
"Elapsed time= " +
Math.round((sinceStart / 1000) * 100) / 100 +
" secs @ " +
currentFps +
" fps."
);
}, fpsInterval);
}

Output:

After 5.31 seconds have elapsed, FPS is 4.9

After 18.34 seconds have elapsed, FPS is 4.91

The setTimeout() method is simply called in the code above at the fps-specified interval rate. RequestAnimationFrame() is called each time setTimeout() is used, which causes the screen to be updated or repainted. All of this happens at the developer-specified frame rate.

Better Approach

We force our animation loop to run at a constant speed by fixing the frame rate. Fixing the frame rate is one method for maintaining consistency. We don’t do this by depending on requestAnimationFrame's execution speed. We instead rely on the intervals between frame updates (aka the frame interval). Let's imagine we want to animate at 60 frames per second. This implies that every 16.67 milliseconds (1 second / 60 frames), our code that updates the visuals must run.

To further highlight the statistics, let’s look at how often our code must execute to animate at 24 frames per second:

1 second / 24 frames = 41.667 milliseconds.

It doesn’t matter how fast or slow requestAnimationFrame is operating while we are running our animation-related code repeatedly at a predetermined time interval. The only factor that counts is the amount of time that has passed since the last call to our animation code.

The code for doing the above is as follows:

// set the expected frame rate
let frames_per_sec = 60;
let interval = Math.floor(1000 / frames_per_sec);
let startTime = performance.now();
let previousTime = startTime;
let currentTime = 0;
let deltaTime = 0;

function animationLoop(timestamp) {
currentTime = timestamp;
deltaTime = currentTime - previousTime;
if (deltaTime > interval) {
previousTime = currentTime - (deltaTime % interval);
// add your visual updates-related code
}
requestAnimationFrame(animationLoop);
}
requestAnimationFrame(animationLoop);

Output:

The above code will play the animation at 60ps as fixed. We fix the frame rate by using requestAnimationFrame as a timing loop and checking the amount of time elapsed (delta time) between each requestAnimationFrame call.

Conclusion

Only when you need to execute quick animations and make sure that browsers will render them smoothly should you choose requestAnimationFrame over CSS animations or setInterval/ setTimeout. Using requestAnimationFrame might be advantageous for animations like leap, stop, pause, and return.

The requestAnimationFrame method only draws animation that the user would see (it doesn't use any CPU resources if you switch tabs, minimize, etc.). There would be no unnecessary frame draws since the browser would only draw frames when it was ready to do so and there would be no ready frames that were waiting to be drawn. So you may choose requestAnimationFrame with confidence since smoothness is guaranteed. If you wish to make a simple animation, you can opt for CSS animations or use setInterval and setTimeout instead.

Originally published at blog.openreplay.com.