How to animate the moon with the canvas element
Gabrielle Davidson
Posted on October 11, 2021
For Hacktoberfest this year, I contributed to a project using the <canvas>
element. I was intrigued, as I'd come across it before while I was learning HTML but always thought "Eh, I'll get to that one day...".
What is the <canvas>
element?
It's an HTML element that gives you the ability to draw with JavaScript. Pretty neat. It takes whatever id
and dimensions you'd like as attributes, and wraps around a backup image which only displays if your drawing doesn't load:
How to animate the moon
You don't have to animate the <canvas>
element but I thought it'd be a nice challenge. I decided to create a waxing and waning moon animation. My approach was to write a function for each phase and loop through them using setTimeout()
.
Lay the foundation
Before anything else, every <canvas>
element must start with two things:
First, we select the <canvas>
element in the HTML with its id
and save it in a variable. Second, we create a variable for the context. This is what we actually draw on. Surprise! The <canvas>
element itself is really just a container. This context variable is what we will use in our functions.
Initialize
I chose a crescent moon as my starting phase. I drew it with a function called init()
and added it as an attribute to the <body>
element we saw before, so that it's called when the page loads.
Repeat with slight variations
I ended up with six very similar functions, so similar that I won't detail each of them here:
- init()
- quarterMoon()
- halfMoon()
- fullMoon()
- halfMoonWane()
- quarterMoonWane()
Each function calls the next one and quarterMoonWane()
calls init()
. That is how the continuous wax/wane effect is achieved. The only differences are the inner(bezier) and outer(arc) curves of each phase. Really it's only four functions, as quarterMoon()
and halfMoon()
are basically equivalent to quarterMoonWane()
and halfMoonWane()
. I repeated them because during the waning phase I needed the same shapes but different setTimeout()
function calls.
Challenges and reflections
The <canvas>
element is no joke. I spent two days working out how to achieve this animation. Granted, it was my first try and I had to do a lot of research and trial and error with tricky math, but it's still a challenging element to work with. Even though I'm glad I got acquainted with it, I can't really think of a situation where I'd want to use it again.
One of the hardest things about it is that you can't see your progress unless you call a method to connect the points you've established (I used ctx.fill()
here, you can use ctx.stroke()
to draw a line instead). It was cumbersome doing that after every line and then deleting them all(except the last one) once I knew what was happening. It makes me wonder if there is an easier way.
I also really wanted the transition between each stage to be a little smoother. I tried speeding up the intervals on setTimeout()
but that didn't give me the effect I was hoping for. I also experimented with window.requestAnimationFrame()
, another method used with <canvas>
, but that made it way too fast by itself. I'm sure there is a way to make it work but I wasn't able to find it after much searching and experimenting.
Finally, since there is a lot of repeated code here, I'm sure there is a more elegant way of achieving this type of animation but in the end it gets the job done and I'm a fan!
Here's a resource for more info on the <canvas>
element and, as always, here's my code if you'd like to inspect in more detail.
Posted on October 11, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.