Before you read: I don't claim to be any kind of expert in canvas. But over the years I have dabbled with it a bit and these are some of my findings. You can also go straight to the fun stuff
Recently I started a new project which had a very cool pattern that made me think of old school Windows struggles. Do you remember that when your computer was really busy downloading stuff you could drag the download window over the entire screen and it wouldn't render correctly?
I created a "simple" codepen a few months ago, for fun it literally has no purpose:
Unknowingly this pen had prepared me for the animation I wanted to make for an actual client (LOVE IT when that happens).
Canvas?
Maybe you've come this far in this article without actually knowing what canvas is or does. Canvas is an HTML element that you can use to draw graphics on a web page with JavaScript. If that sounds weird, I got you, it is a little weird. BUT it also means you can do crazy stuff like interacting with events an animating all the things!
First thing you need to do is select the canvas element with JS and get the "context" which is the drawing object. That is really just a weird way of saying that it's the part of the canvas which you'll mention when changing anything.
<canvas id="canvas"></canvas>
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
// Don't ask me why this is called ctx and canvas is written out entirely, it doesn't make sense but seems to be the standard.
An image constructor allows you to add an image with JavaScript. You can also add an event listener to check if it has loaded.
There are multiple properties that can be applied to the context of the canvas.
I used ctx.drawImage()
to add the image to the center of the window on image load. 🧐
const image = new Image();
image.src = 'http://api.cydstumpel.nl/wp-content/uploads/2020/01/graphic.png';
image.addEventListener('load', e => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// in this case I want to make the canvas as high and wide as the window
ctx.drawImage(image, window.innerWidth / 2 - image.width / 2, window.innerHeight / 2 - image.height / 2, image.width, image.height);
// calculating the space left and on top of the image to center it on the page by subtracting half of the page by half of the image.
});
I added some event listeners to check when the mouse/touch event is "down", "up" and "moving". The moving event should only be fired when the mouse is down (clicked) and not when it's just hovering over the page.
I add the coordinates of the last 5 events fired to an array to add a windows error like effect.
// [...]
let lastImages = [];
// declare lastImages as an array in the global scope
// [...]
canvas.addEventListener('mousemove', (e) => {
x = e.clientX - image.width / 2;
y = e.clientY - image.height / 2;
// to make sure the image will be centred around the mouse remove half the width and height respectively
// Save coordinates to last images as long as it's < 5
if (lastImages.length < 5) {
lastImages.push({x, y});
} else {
// Delete first item in array (first item is last added) and add new coordinates
lastImages.shift();
lastImages.push({x, y});
}
// The code above makes sure that there are not more than 5 elements on the screen at the time, adding more could definitely slow down your browser.
if (mouseDown) {
// check if mouse is down before firing drawIt function
window.requestAnimationFrame(drawIt);
}
});
The draw function:
function drawIt() {
if (mouseDown || popping) {
// Clear entire canvas because clearing just the image doesn't work unfortunately
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw new image for each item in lastImages array
// For loops are faster than foreach I read somewhere
for (let i = 0; i < lastImages.length; i++) {
// After clearing the entire canvas draw the images from the lastImages array again
ctx.drawImage(image, lastImages[i].x, lastImages[i].y, image.width, image.height);
}
}
}
The popIt
function is called when mouse goes up or out:
function popIt() {
mouseDown = false;
// Okay this is a bit gross, I know about intervals this just works better:
for (let i = 0; i < 5; i++) {
setTimeout(removeLastItems, 50 * (-i * 1.2) );
// -i * 1.2 creates an increasingly faster timeout simulating a fade in bezier
}
}
The removeFirstItems function shifts (removes) the first item of the array (which is equal to the first item that was added to the array)
if (!mouseDown && lastImages.length > 1) {
popping = true;
// Get first drawn image and remove it from array (first drawn is first in array)
lastImages.shift();
drawIt();
// initiate drawIt function without the request keyframe for it's only fired 5 times
}
Fun stuff
You should really check this out on a large screen, it works a lot better when you have a lot of space to drag.
Ask me anything
If you don't understand my rambling, or don't agree with it don't hesitate to add your comment to this article. :)
Big thank you to commenters last week who mentioned I should really think about debouncing events, I still need to actually implement it in my portfolio though😅.
Top comments (1)
I love HTML canvas so really enjoyed your post.
It great when fun experiments help with client work, this happened to me also.
PS: That windows dialog not redrawing properly sure bought back a few memories :)