Animate Canvas with Generators
Published 9/18/2021 by Colby RabideauI'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!