Welcome back to my another blog post. In today's tutorial we will learn to create a basic animation using HTML Canvas element in React component. This approach can also be used in Next.js projects.
Creating a Canvas element
We begin by creating a simple HTML Canvas element and add the width
and height
attributes.
export function Example() {
return <canvas width={500}
height={300}
style={{border: '1px solid #333'}} />
}
Now, we create a reference to the canvas using the useRef
hook. We will use this to get the canvas context.
import {useRef} from 'react';
export function Example() {
const canvasRef = useRef(null);
return (
<canvas ref={canvasRef}
width={500} height={300}
style={{ border: '1px solid #333' }} />
);
}
Trying to get context directly will throw error
If we try to get the context of the canvas while the component is not yet mounted, we will get error like the following.
import {useRef} from 'react';
export function Example() {
const canvasRef = useRef(null);
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
return (
<canvas ref={canvasRef}
width={500}
height={300}
style={{ border: '1px solid #333' }} />
);
}
Error:
TypeError: Cannot read properties of null (reading 'getContext')
Get the canvas context using useEffect
hook
To solve the above error we have to first let the component mount and then inside the useEffect
hook we will get the canvas context. For this we will write the following code.
import { useRef, useEffect } from 'react';
export function Example() {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
}, []);
return (
<canvas
ref={canvasRef}
width={500}
height={300}
style={{ border: '1px solid #333' }}
/>
);
}
Draw circle and fill it with color
We will draw a circle at the center of the canvas. Our canvas width is 500px
and height is 300px
. So, our circle should have it's center at 250px along x-axis and 150px along y-axis inside our canvas.
Note! Origin is at the top left corner of the canvas. The x-axis moves from top left corner to top right corner. Similarly, the y-axis moves from top left corner to the bottom left corner.
We can extract the width and height of the canvas from the canvas
variable we created above. And let's create a circle that has 30px radius. For this we will write the following lines inside the useEffect
hook.
const { width, height } = canvas;
let radius = 30;
Now, to draw the circle we use the arc
method. We pass 5 arguments to it.
arc(x, y, radius, startAngle, endAngle)
Where, x
and y
is for the center of the circle. The radius
is for the radius of the circle and it must be a positive value. The arc start from the positive x-axis. And it is drawn clockwise.
Check MDN on arc
method.
Write the following code in the useEffect
hook.
import { useRef, useEffect } from 'react';
export function Example() {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
const { width, height } = canvas;
let radius = 30;
// draw circle and fill it
context.clearRect(0, 0, width, height);
context.beginPath();
context.fillStyle = '#333';
context.arc(width / 2, height / 2, radius, 0, 2 * Math.PI);
context.fill();
}, []);
return (
<canvas
ref={canvasRef}
width={500}
height={300}
style={{ border: '1px solid #333' }}
/>
);
}
At this point if we open our page in the browser we will have a circle at the center of the canvas.
Creating animation loop
We can create an animation loop by writing the following code inside the useEffect
hook.
const animate = () => {
requestAnimationFrame(animate);
};
animate();
So, we are creating an animate
function and inside it we are calling requestAnimationFrame
function and passing the animate
function name. This will create a loop.
And inside the useEffect
hook, at the end, we are invoking the animate
function which starts the loop. Now, we will move the logic to draw the circle inside the animate
function.
const animate = () => {
// draw circle and fill it
context.clearRect(0, 0, width, height);
context.beginPath();
context.fillStyle = '#333';
context.arc(width / 2, height / 2, radius, 0, 2 * Math.PI);
context.fill();
requestAnimationFrame(animate);
};
animate();
This will clear the canvas and draw the circle and repeat in loop. Since this is happening very fast so, it is not giving us the animation feel. Let us try to change the radius and oscillate it between 10px to 50px.
Changing the radius to create animation
Inside our animate
function we will increase the radius of the circle as long as it is less than 50px. As so as it becomes 50px, we will start to decrease the radius as long as it is greater than 10px. Once the radius reaches 10px, we will start to increase the radius again and thus the radius will oscillate between 10px and 50px.
We will create a dr
variable which controls the rate at which the radius increases or decreases. In our case, we will change our radius by 1px.
let dr = 1;
Now, let us write the logic to oscillate the radius between 10px and 50px.
if (radius >= 50 || radius <= 10) {
dr *= -1;
}
radius += dr;
Final code
Following code will create a circle whose radius will oscillate between 10px to 50px.
import { useRef, useEffect } from 'react';
export function Example() {
const canvasRef = useRef(null);
useEffect(() => {
const canvas = canvasRef.current;
const context = canvas.getContext('2d');
const { width, height } = canvas;
let radius = 30;
// this will control the speed of radius change
let dr = 1;
const animate = () => {
if (radius >= 50 || radius <= 10) {
dr *= -1;
}
radius += dr;
// draw circle and fill it
context.clearRect(0, 0, width, height);
context.beginPath();
context.fillStyle = '#333';
context.arc(width / 2, height / 2, radius, 0, 2 * Math.PI);
context.fill();
requestAnimationFrame(animate);
};
animate();
}, []);
return (
<canvas
ref={canvasRef}
width={500}
height={300}
style={{ border: '1px solid #333' }}
/>
);
}
Following GitHub repository contains Next.js example.
This brings us to the end of this tutorial. Hope it was interesting. See you soon in my next videos and blog posts. Feel free to subscribe my YouTube channel @yusufshakeel. Have fun coding.