Exploring the HTML5 Canvas Tag Through Video Game Cameras

First things first, this is going to be much easier to follow if you read my previous story about creating a basic game engine with Javascript using the Canvas tag. We’ll be using a lot of code from it to speed this whole process up.
Against my better judgement I’ve forged ahead and started creating a camera system using the drawImage() function built into the context rendering system that HTML5 uses. I’ve restructured a few things to improve the engine, most importantly the array that contained our instances of entities is now an object with unique keys for each object. You can implement this by using the code below.
class Entity {
constructor(x,y) {
this.x = x
this.y = y
this.name = 'Entity"
this.fIndex = `${entityCount}`
functionLoop[this.fIndex] ||= this
entityCount += 1
this.sprite = '' }
loop(){
}
draw(){
let img = document.getElementById(this.sprite)
ctx.drawImage(img, this.x, this.y)
}
destroy() {
delete functionLoop[this.fIndex]
}
}
This is the code that every Entity needs to inherit. We’re eventually going to add more, but to break it down think of these as lifecycle methods. The constructor method sets the entity up, the loop contains the logic that will be executed by the Entity each clock cycle, the draw method is how the Entity will be drawn to our canvas, and the destroy method completely removes its entry in the functionLoop object, freeing a bit of space in memory. All in all this improves the structure and makes the engine more predictable.
We then need to update our clock cycle function to the following.
function fireClock() {
document.dispatchEvent(clockTick)
count += 1 //We'll don't use this currently, but it's good to have a variable incrementing everyframe for the sake of animation later
ctx.fillStyle = "#000000"
ctx.fillRect(0, 0, displayWidth, displayHeight) for (const object in functionLoop){
functionLoop[object].loop()
} for (const object in functionLoop){
functionLoop[object].draw()
}
}
This is a small change, but you may be asking what that displayWidth and displayHeight are doing there. This is where the fun begins. The canvas tag doesn’t actually need to be shown on screen. You can store it in a variable and keep it as a buffer for displaying more complex and dynamic images. Today we’re going to implement a background that scrolls with a player controlled object. First we need to add a div to our HTML file with the class “full-screen.” Then, above the file that contains our fireClock() function we’re going to add the following lines.
let sceneWidth = 320
let sceneHeight = 288let cameraWidth = 160
let cameraHeight = 144
let cameraX = 0
let cameraY = 0let displayWidth = 1280
let displayHeight = 1152const bufferCanvas = document.createElement("canvas")
bufferCanvas.width = sceneWidth
bufferCanvas.height = sceneHeightconst mainCanvas = document.createElement("canvas")
const ctx = bufferCanvas.getContext("2d")
const mainCtx = mainCanvas.getContext("2d")const screenHolder = document.querySelector("#screen-holder")screenHolder.append(mainCanvas)
Ok. So what’s happening here? Simply put we’re building a buffer that we will draw to our graphics onto and THEN we draw that buffer to the main canvas. The reason being is HTML5’s spiffy drawImage() function. You can give the drawImage() function differing numbers of arguments to have it do neat and useful things. For our purposes we need to focus on the following iteration.
drawImage(image, source X, source Y, source Width, source Height, destination X, destination Y, destination Width, destination Height)
This means we can draw a portion of our buffer canvas to the main screen. Lets add the following line to the end of our fireClock() function.
mainCtx.drawImage(bufferCanvas, cameraX, cameraY, cameraWidth, cameraHeight, 0, 0, displayWidth, displayHeight)
Now we’re using those variables we defined to slice a small rectangle out of our full image and display it on main canvas.

if we create a background image to display to the buffer we can get create a little test environment to see the effects of our work. First lets add a background image to our project (here’s a fun one). Pop it into the dom via your HTML file and define this image as a const called backGround. Then we add the following line to our fireClock() function just before our first loop.
ctx.drawImage(backGround,0,0)
Now, anytime we draw to the buffer canvas, our sprites will be drawn on top of the background. Now we need a little stooge to walk around. Let’s make a new Player Entity.
class Player extends Entity{
constructor(x,y) {
super(x,y)
this.sprite = //You can use whatever image you want here, I recommend one that is 16x16 pixels as it will scale best. Store the image on the Dom and give it an id that you can place here.
this.name = "player"
this.hsp = 0
this.vsp = 0
}
loop() {
this.hsp = ((right) - (left))
this.vsp = ((down) - (up))
this.x += this.hsp
this.y += this.vsp if (this.x < 0){this.x = 0}
if (this.x > sceneWidth - 16){this.x = sceneWidth - 16}
if (this.y > sceneHeight - 16}{this.y = sceneHeight - 16}
if (this.y < 0){this.y = 0} cameraX = this.x - (cameraWidth/2)
cameraY = this.y - (cameraHeight/2) cameraX < 0 ? cameraX = 0 : null
cameraX > sceneWidth - cameraWidth ? cameraX = sceneWidth - cameraWidth : nullcameraY < 0 ? cameraY = 0 : null
cameraY > sceneHeight - cameraHeight ? cameraY = sceneHeight - cameraHeight : null
}
Now, just before we initiate our clock interval we can just create a new player entity at 16x and 16y and move it around the screen. Behold! A functioning camera!
You can extrapolate from here and think of a ton of cool effects you can do with this simple implementation. You can create parallax layers, or use it for zooming in on a product on a store page. This is an incredibly useful effect even outside of video games.
In our next foray into Javascript game engine design we’re going to implement tilemaps! Be sure to follow if you’re interested. In the mean time consider cloning down a version of my engine here and poking around! Enjoy!