FrontyBlog

Simple drawing and Animation inside canvas with React

8/30/2020

Sometimes hard to find some basic steps to make some features, for me some time ago it was integration react with canvas and adding animation. Here I'll show basic steps that should help to jump into this stack and made a simple interaction.

Let's start with some code.

Define our first component and draw first rectangle figure.

function CanvasComponent() {
const canvasRef = useRef(null);
useEffect(() => {
let canvas = canvasRef.current;
let ctx = canvas.getContext("2d");
ctx.fillStyle = "tomato";
ctx.fillRect(10, 10, 50, 50);
});
return <canvas ref={canvasRef} width="300" height="200" />;
}

Initialize link to our dom element with help of useRef, and now we have a connection with our canvasт, and then through useEffect we are getting inner canvas context engine (ctx) and draw a rectangle.

We have our rectangle, time to add animation

To initialize animation on the canvas we need to write our custom function that will be responsible for animation with the help of requestAnimationFrame

function widthAnimation(start, ctx) {
return function step(timestamp) {
if (!start) start = timestamp;
let progress = timestamp - start;
let limit = 280;
let xValue = Math.min(progress / 10, limit);
ctx.fillRect(xValue, 10, 50, 50);
if (xValue < limit) {
window.requestAnimationFrame(step);
}
};
}

Passing 2 arguments inside to our function, first one it's initial value for step, and the second one its context of our canvas that we are saving to the scope with the first initialization, this function return another recursive function step that is will be repeated till our value do not get threshold in our case it's 200 ( limit = 280 ).

And now we call our animation function inside useEffect, inside animationFunc we are passing our parameters, and then call animation window.requestAnimationFrame(animation

useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
ctx.fillStyle = "tomato";
ctx.fillRect(10, 10, 50, 50);
let animationFunc = widthAnimation(null, ctx);
window.requestAnimationFrame(animationFunc);
});

Something goes wrong, we wanted to move rectangle from one corner to another, but we just increase his width

This is specific of canvas drawing, after each drawing we have previous state of all our drawing on the canvas, it means all this path from 0 to 280 we have multiple amounts of rectangels. To fix this we need clear our canvas context before each drawFunction like this ctx.clearRect(0,0,300,200

return function step(timestamp) {
//...
ctx.clearRect(0, 0, 300, 200)
ctx.fillRect(xValue, 10, 50, 50)
//...
}

/ gif with correct behavior

Controls for Animation

Here is how we can do our animation contollable

Creating useState for toggle animation

const [isActive, toggleAnimation] = useState(false);

Adding controls to Run and Reset state

<div>
<button
style={styles.button}
type="button"
onClick={() => toggleAnimation(true)}
>
Run
</button>
<button
style={styles.button}
type="button"
onClick={() => toggleAnimation(false)}
>
Reset
</button>
</div>

And now we need add to our useEffect condition at the onset of which we are running animattion

useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
let animationFunc = widthAnimation(null, ctx);
let animation = null;
ctx.clearRect(0, 0, 300, 200);
ctx.fillStyle = "tomato";
ctx.fillRect(10, 10, 50, 50);
if (isActive) {
animation = window.requestAnimationFrame(animationFunc);
}
return () => {
window.cancelAnimationFrame(animation);
};
});

One important part, we should clear / reset our hook with the return function , it's should be done to prevent performance issue and possible bug

useEffect(() => {
...
return () => {
window.cancelAnimationFrame(animation)
}
})

As a conclusion

Here we are met how is working simple drawing inside canvas with React, and add basic animation interaction requestAnimationFrame with useEffect.

Simple Canvas Animation MDN.

Cleanup useEffect.

© 2020 Vitaly Yastremsky