The Canvas
Okay, so the canvas element is both amazing frustrating at times. Either way, it's exactly what everyone wished we had in the 90's and 00's. Canvas is in that wonderful place where they're both very new and exciting and also fully supported by modern browsers. So it's high time we work with it and make our websites better with it.
Let's start with a simple 2d context. With canvas you have the option of 2d or 3d rendering. For this course I'm going to keep it to 2d, because the ideas are the same and 3d makes it more complex. Perhaps another time.
<!DOCTYPE html>
<html lang="en">
<meta charset="UTF-8">
<head>
<style>
body {
margin: 0;
background: black;
}
canvas {
height: 100vh;
width: 100%;
}
</style>
</head>
<body>
<canvas></canvas>
</body>
<script></script>
</html>
And in your javascript file or script tag.
const canvas = document.querySelector("canvas");
const context = canvas.getContext("2d");
const resize = () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
}
window.addEventListener('resize', resize);
resize();
context.fillStyle = 'green';
context.fillRect(0, 0, canvas.width, canvas.height);
Here we have 16 of the simplest setup lines of code you can imagine. It initializes the canvas element, and resizes it to the full height and width of the window and reacts to browser resize. Cool. Awesome base to start from.
Now, canvas is both amazing and incredibly under engineered. Perhaps with a bit more interest, browser vendors will implement some of my wishlist features. But we'll get to those a bit later. For now, let's figure out how to draw stuff and how to debug when something isn't appearing when it should.
Validate your arguments
Canvas does this really annoying thing where if you try and draw on it and you do the wrong thing. Like you leave out an argument or put the wrong type, it will not error, it will show no clue as to what happened. Very sad. So what I suggest is to wrap the canvas browser api functions with a validation function that checks and errors if any are missing. That way you will always know.
A lot of canvas is like this. Very minimal and bare bones. It's honestly kinda cool in that it allows us to treat it a lot more like a systems programmer and solve all of the problems with our own code, to our own liking.
So here's the rectangle function we can use to clear the canvas on every frame.
function validate() {
const args = Array.from(arguments);
args.map((a, i) => {
if (typeof a === 'undefined' || a == null) {
const error = validate.caller.name
console.error(`In ${error}(): Argument missing (${args.join(', ')})`);
}
});
}
function rect(color, x, y, w, h) {
validate(color, x, y, w, h)
context.fillStyle = color;
context.fillRect(x, y, w, h);
}
rect('red', 200, 100, null, 100);
If we take any of the arguments off the rect call, we get a nice console error about it. Notice we can only use the "magic" arguments keyword on functions declared using the older syntax. const a = () => {}
doesn't have arguments.
Anyway, let's get into a real world example and convert our starter here to a real project.
In index.js
:
let CANVAS, CONTEXT
const load = () => {
CANVAS = document.querySelector("canvas")
CONTEXT = CANVAS.getContext("2d")
}
const resize = () => {
CANVAS.width = window.innerWidth;
CANVAS.height = window.innerHeight;
}
window.addEventListener("load", load)
window.addEventListener("resize", resize)
If the document has a canvas in it it'll pull the 2D context out andplace them into the module variables CANVAS
and CONTEXT
.
So to create a background we need to draw a box. You can also set the color in the HTML space of the background with css, but let's control everything in the 2d context.
const box = (color, x, y, w = 100, h = 100) => {
CONTEXT.fillStyle = color;
CONTEXT.fillRect(x, y, w, h);
}
const background = () => {
box('black', 0, 0, CANVAS.width, CANVAS.height)
}
In Sum
We've demystified the canvas, all without putting any extra code into our game. We've got our first class and a real structure for the project moving forward. From now on I'll stick to index.js for much of the course. You may structure your game however you'd like. The draw back to multiple files, is new people looking at it will have to refer to those files to understand anything. Where as my single file can be read form top to bottom.
Let's add a loop to this game so we can get something moving on the screen.