Creating Beautiful HTML5 Canvas Animations
Canvas animations are a great way to add interactive content and visual customization to any website. By combining HTML, CSS, and Javascript, you can build shapes and controls using a bit of math. Canvases can be used to draw graphs, combine photos, create animations, or even full fledged video games.
This tutorial shows the basics of canvas animation, an demonstrates how the 'Synthwave' theme on the homepage is built.
Drawing Triangles
To begin with we'll start with a simple example of drawing static triangles on a canvas. We'll start by grabbing some basic information from our canvas, including using the 2d context which has a back reference to it's associated DOM element:
var canvas = document.querySelector('#canvas'),
ctx = canvas.getContext('2d'),
height = window.innerHeight,
width = window.innerWidth
Using this DOM element, we can tell the canvas to draw a path. Below we have an example of drawing a single triangle.
// drawing a single triangle
ctx.beginPath();
ctx.moveTo(x + size, y + size);
ctx.lineTo(x, y + size);
ctx.lineTo(x, y);
ctx.fill();
Extending on this, we can expand our pattern to fill the entire canvas with our triangle pattern. Canvas drawings have a 0,0 coodinate in the top left corner, and then positive integers as the coordinates go down and right in x/y plane.
function drawPattern() {
for(let x = 0, y = 0, size = canvas.height / 20; y < canvas.height; x += size * 2) {
// If next pattern doesn't fit canvas width, start new row
if(x + size >= canvas.width) {
x = 0; //start new row on the left side
y += size * 2
}
rightTrianglePattern(x, y, size)
}
}
Putting it all together with a few event listeners allows us to to launch the canvas drawing after the window load, and to redraw the canvas when the window size changes.
Animations, loops, and requestAnimationFrame
There are many ways to add movement to the canvas, but in their core concept, they all involve a type of loop. While it might make the most sense simply to use an infinite loop - this often leads to memory leaks. That's where request animation comes in:
The window.requestAnimationFrame() method tells the browser that you wish to perform an animation and requests that the browser calls a specified function to update an animation before the next repaint. The method takes a callback as an argument to be invoked before the repaint.
Request animation frame is far superior to setInterval or setTimeout as the problem is that timing of the loop or interval often forces the browser to animate at very different rate than the monitor's refresh rate. If the animation timeout or interval timing is less than the animation execution time, this can also lead to a stacking call of animation functions, leading to a heavy CPU costs over time for no additional animation input.
Request animation frame also helps by giving the browsers the ability to prevent the next frame from being called when the browser tab is no longer in focus, or the browser has been minimized.
function animate() {
let arr = [];
let delay = 0;
arr = arrTriangles;
//on update if in view
inView = canvas.getBoundingClientRect().bottom > 0;
if (inView) {
updateAll(arr);
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < arr.length; i++) {
arr[i].render();
}
}
if (debounceTimer) {
window.clearTimeout(debounceTimer);
}
debounceTimer = setTimeout(function () {
requestAnimationFrame(function () {
animate();
});
}, delay);
}
Testing for Canvas in View and adding a Debounce Timer
We can further optimize our canvas animation by adding a debounce timer to the resize, scroll (if we had one), and even requestAnimationFrame itself to throttle calls according to a specific timer. A debounce timer works by preventing the browser from calling the function too many times within a set lockout period. This is especially import for events like scroll and resize.
Without a debounce timer, actions like a scroll event can be fired 100s of times in just a few seconds. When it comes to a canvas, that usually means the browser is going to attempt to redraw your canvas hundreds of times, and kill performance. In the resizeCanvas
function below, one can see how the debounce timer for the resize event in the rainbow train
function resizeCanvas() {
clearTimeout(timeoutId);
timeoutId = setTimeout(function () {
let w = (canvas.width = document.body.offsetWidth);
let h = (canvas.height = document.getElementById("sky").offsetHeight);
arrTriangles = createTriangleArray(w, h);
}, 100);
}