1 - The Canvas

There are multiple ways to render things to a webpage. We’re going to choose the <canvas> element as it provides the most control. Given the declarative nature of our approach though the drawing routines provided by the canvas API are not going to be used. Therefore we’ll create a proxy class to wrap the element and hide the dirty details:

// lib/Canvas.js
class Canvas {
    #canvas = document.createElement('canvas')
    #ctx = this.#canvas.getContext('2d')
    constructor({container, height, width}) {
      Object.assign(this.#canvas, {height, width})

The private property #canvas holds the actual HTML element. The constructor accepts a height and a width for defining the initial size and a container for the html element that will contain the canvas . The #ctx property references the drawing api of the canvas. The canvas is transparent by default.

We want to keep this class simple so we’ll limit the ability to draw on it by providing a single method called draw:

// lib/Canvas.js
class Canvas {
    draw({imageData, top, left}) {
        this.#ctx.putImageData(imageData, top, left)

The drawing context requires an ImageData object and a position therefore our draw method must accept the same. The ImageData object contains a one-dimensional array that represents pixels in RGBA order with integer values between 0 and 255. That’s 24 bits for the color channels plus 8 bits for the alpha channel. This color depth is referred to as True Color. The initial value of the array is zero filled which represents transparent black.


For example if the dimensions are 640x360 then the size of imageData is width x height x 4 bytes = 921,600 bytes. With different dimensions you can see that the memory requirements would change significantly.

A note on style: you’ll notice that parameter destructuring is used in the definition of draw. Named parameters are preferable to positional for a couple reasons:

  1. It makes it obvious what the actual parameters are without having to inspect the implementation. Ex: repo.find(12,140,53202) vs repo.find({age: 12, weight: 140, zip: 53202})
  2. Extensions can be made without impacting existing clients Ex: repo.find({name: 'bob', age: 12, weight: 140, zip: 53202})

This style of named parameters will be used in all future examples.

Source code for this lesson.


You can create, reply to, and manage comments on GitHub