Animate Canvas with Generators

Published 9/18/2021 by Colby Rabideau

I've enjoyed working with canvas recently. A couple weeks ago I was making a demo to create the RC logo by drawing squares.

The first version of the code was a bunch of canvas instructions one after the other.

const ctx = canvasElement.getContext("2d") ctx.fillStyle = "white" ctx.fillRect(0, 0, canvasElement.width, canvasElement.height) ctx.fillStyle = "black" ctx.fillRect(10, 290, 280, 80) ctx.fillRect(30, 270, 240, 20) // and so on and so forth...

I wanted a simple animation to illustrate the steps of the program. I split the logic up into an array of functions and called each one in sequence using setInterval.

const instructions = [ () => { ctx.fillStyle = "white" ctx.fillRect(0, 0, canvasElement.width, canvasElement.height) }, () => { ctx.fillStyle = "black" ctx.fillRect(10, 290, 280, 80) }, () => { ctx.fillRect(30, 270, 240, 20) }, // ... ] let step = 0 let intervalId = setInterval(() => { if (step < instructions.length) { instructions[step]() step++ } else { clearInterval(intervalId) } }, 500)

That worked just fine. I guess I had to type "() => {}," a lot but it did the job. Then it occured to me: maybe instead of a list of functions I could use a generator.

Generators

A generator function is a special syntax for creating objects that implement the iterable protocol.

function* makeGenerator() { yield 1 yield 2 yield 3 } const generator = makeGenerator()

The yield keyword works like return, but each time we call generator.next() the program starts after the previous yield.

generator.next() // {value: 1, done: false} generator.next() // {value: 2, done: false} generator.next() // {value: 3, done: false}

Onces you've made it through all the yields, you can keep calling generate.next() but done will always true and nothing else will happen.

generator.next() // {value: undefined, done: true} generator.next() // {value: undefined, done: true} generator.next() // {value: undefined, done: true}

Putting it together

Getting back to the logo animation, I put my canvas instructions into a generator with a yield between each step.

function* makeLogoGenerator() { ctx.fillStyle = "white" ctx.fillRect(0, 0, canvasElement.width, canvasElement.height) yield ctx.fillStyle = "black" ctx.fillRect(10, 290, 280, 80) yield ctx.fillRect(30, 270, 240, 20) yield // ... }

Then I call logoGen.next() in setInterval, until the generator is done.

const logoGen = makeLogoGenerator() const intervalId = setInterval(() => { if (logoGen.next().done) clearInterval(intervalId) }, 500)

Calling generator.next() over time creates a simple animation effect on the canvas. It works pretty well!

The working demo is here and the source is on GitHub.