Animations are key to developing interactive and engaging websites. A simple animation can be created with setInterval
or setTimeout
functions to change the properties of the elements after a defined interval. Some of the common animations like fade in/out, slide in/out, or collapse/expand are often used to display a change on a page to provide a meaningful transition effect to the user.
Animating an element involves reflow (changes in height, width, font, etc.) and repaint (changes in element color, background, etc.), which occur due to changes in the Document Object Model (DOM) to render or update a page. Faulty or slow animation can cause a sluggish effect on the screen due to multiple invocation of reflow/repaint processes.
Animations can be created using CSS but it is not suitable for non-linear animation that requires input from various sources such as users or functions. This guide covers the use of the requestAnimationFrame
method to create optimal animations in react. The codebase is available on github.
A rendering process includes the calculations of layout and style changes in DOM to update the page. DOM represents an HTML document in terms of nodes, and each node can further have information or style (from CSS) nodes. Changing DOM is expensive and time-consuming, so React uses a virtual DOM to track the changes and then only apply the new changes in the actual DOM by using a diffing algorithm.
A series of frames make an animation, and each frame is being rendered on the screen one after another. As a rule of thumb, animation should be able to render 60 frames per second for smooth experience, but the browser or system may not always be able to process 60FPS. Consider the example when a user has opened 20-30 links simultaneously.
A simple animation can be created using setTimeout
and setInterval
. The setTimeout
is executed after a provided delay and needs to be called recursively whereas setInterval
repeatedly executes the code after the defined interval:
1// move element down by 3px, 60 times in a second
2import React, { Component } from 'react';
3import { render } from 'react-dom';
4import './App.css';
5
6export default class App extends Component {
7 constructor() {
8 super();
9 this.state = {
10 name: 'Football Animation'
11 };
12 }
13
14// change the top position by 3 pixels of an element that has a “circle” class value.
15 moveBall = () => {
16 let start = Date.now();
17 let football = document.querySelector(".circle")
18
19 let timer = setInterval(function () {
20 let interval = Date.now() - start;
21
22 football.style.top = interval / 3 + 'px'; // move element down by 3px
23
24 if (interval > 1000) clearInterval(timer); // stop animation
25
26 }, 1000 / 60);
27 }
28
29
30 render() {
31 return (
32 <div className="containter">
33 <img className="circle" onClick={this.moveBall} />
34 </div>
35 );
36 }
37}
1.App {
2 width: 100vw;
3 height: 100vh;
4}
5
6.circle {
7 left: 0;
8 right: 0;
9 margin: 0 auto;
10 position: fixed;
11 width: 75px;
12 height: 75px;
13 background-position: center;
14 background-image: url("https://img.icons8.com/officel/80/000000/football2--v2.png");
15 background-color: #ffffff;
16 border-radius: 50px;
17 background-repeat: no-repeat;
18}
The above snippet will move the element that has the circle class value down, which works fine but there are some drawbacks:
setInterval
is not guaranteed to be called at the given delay due to a slow or busy system.To optimize system and browser resources, it is recommended to use requestAnimationFrame
, which requests the browser to execute the code during the next repaint cycle. This allows the system to optimize resources and frame-rate to reduce unnecessary reflow/repaint calls.
The requestAnimationFrame
method only calls the input animation function when the browser is ready to perform the paint operation. The earlier example of the moveBall
function can be implemented simply by replacing setInterval
with requestAnimationFrame
:
1moveBall = () => {
2 let start = Date.now();
3 let football = document.querySelector(".circle")
4 // timestamp: time elapsed in milliseconds since the web page was loaded
5 let timer = requestAnimationFrame(function animateBall(timestamp) {
6 let interval = Date.now() - start;
7
8 football.style.top = interval / 3 + 'px'; // move element down
9
10 if (interval < 1000) requestAnimationFrame(animateBall); // queue request for next frame
11
12 });
13}
The requestAnimationFrame
method only executes the callback function once per request and needs to be called again with requestAnimationFrame(animateBall)
to perform the next animation frame transition. The requestAnimationFrame
method returns an integer ID which can be used to cancel the queued request using the cancelAnimationFrame(id)
method. The animation callback function can also accept a timestamp value, which is the time elapsed in milliseconds since the web page was loaded. It provides the exact timestamp when the callback is executed.
Let's implement the bounce animation to make the ball animation more interesting and natural. To implement the bounce, two methods are required, calculating the bounce factor for y axis:
1bounce = (timeFraction) => {
2 for (let a = 0, b = 1; 1; a += b, b /= 2) {
3 if (timeFraction >= (7 - 4 * a) / 11) { // 4 and 7 coefficient are used to control bounce and smooth y axis fall
4 return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) // Math.pow(b, 2) to keep the same x axis for bounce
5 // -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) adjust the y axis up and down
6 }
7 }
8}```
9
10and the `easeOut` function that takes an animation methods and returns a wrapper method to convert the bounce y-axis factor downwards:
11```JS
12
13 easeOut = (timing) => {
14 return (timeFraction) => {
15 return 1 - timing(1 - timeFraction);
16 }
17 }
Now the bounce function will look like below:
1 bounceBall = () => {
2 let bounceEaseOut = this.easeOut(this.bounce);
3 let start = Date.now();
4 let football = document.querySelector(".circle")
5 let id = requestAnimationFrame(function animate(time) {
6 let interval = (Date.now() - start) / 2000;
7 if (interval > 1) interval = 1;
8
9 football.style.top = bounceEaseOut(interval) * 300 + 'px' // adjust the y axis
10 // football.style.left = interval * 200 + 'px' // adjust the x axis
11
12 if (interval < 1) {
13 requestAnimationFrame(animate);
14 }
15
16 })
17 }
requestAnimationFrame
is supported by all modern browsers, and support goes back to IE10.transform
methods when possible for better performance.requestAnimationFrame
also allows the browser to block the animations to save battery when the page is not active or the system is under-performing.The optimized codebase is available on my bounce-react-animation repository. This guide explained the necessary details to get started with animations in React with requestAnimationFrame
. Happy coding!