Animation is often the last frontier for a developer. Many regard it as a seriously difficult challenge, and so put off attempting it. Which is a shame, because animation on a computer is not all that different from the kind of flipbook animation you probably did as a kid in the corner of your school books. In fact, it’s probably even easier than that.
The essential skills needed for HTML5 animators
You don’t need every skill on every project, but to really consider yourself a master of the art, you will need to know how to do all of them. This is the complete list:
- Draw frames
- Update the screen at intervals
- Move objects around
- Play sounds
So what we’re going to make today is a clock, which allows us to use all of these skills together in one project.
Why a clock?
A clock is a very simple shape, so it is easy to draw. It has moving parts that move in a predictable way. And the parts must move at very precise intervals.
Starting the document
It’s quite probable that if you’ve reached this page you already know how to create a standard blank HTML page, but just in case this is your first ever attempt, here’s what one looks like:
Simple enough, right? Don’t worry about trying to copy and paste the code. You’ll be able to download all the source code from a link at the end of the article.
Setting up the canvas
The canvas you create is just one line of HTML code which goes between the <body></body> tag pair you already created, like so:
If you’ve never set up a canvas in HTML5 before, you probably thought it was going to be more difficult than that. If you’re wondering why the height and width values are 800 x 450, it’s an aspect ratio thing. This translates to a 16:9 aspect ratio. It’s a good idea to get accustomed to working in 16:9 perspective.
Note that we’re using HTML attributes to set the height and width rather than using CSS. This is because using CSS to set canvas height and width can produce unexpected results in some browsers. The canvas element expects you are going to supply these values, and if you don’t, it will use it’s own default values (300×150).
Just in case that is not clear: if you fail to provide the width and height values when you declare the canvas object, and then you set the values in CSS, the browser will stretch a 300×150 pixel image to fit a space of 800×450 pixels, so it’s going to look very strange. It’s modifying an image with a 2:1 aspect ratio to fit a space designed for a 16:9 aspect ratio. The image gets stretched horizontally to 267% of its original size and vertically to 300% of its original size.
Set up a code block
For this part, you set it after the <body> tag but before the </html> tag. The only reason is that some browsers stop reading after they reach </html>, so if you put the code block after that tag, it might be ignored.
There’s just six things we need to add before our animation is complete. These things are:
- Script initialization
- Math stuff
- Geometry stuff
- Drawing stuff
- Animation stuff
- Reaction stuff
Amazingly enough, that is all that needs to be done, and you won’t believe how easy it is.
Creating script initialization
To make programs more efficient and reusable, we normally divide them into functions. One price that must be paid when using functions is that we must have some way to trigger them. For code that executes on web pages, you’ll normally want to add a delay, so that the page has time to load everything before the script starts doing any work.
There are a few different methods to introduce the necessary delay, and which one to use depends a little bit on what you’re going to do. In the case of this project, we’re going to use the DOMContentLoaded event to do the job.
Thus DOMContentLoaded is handy because it doesn’t introduce any more delay than is strictly necessary. This is different to something like $(document).ready() in jQuery (which is a method you’ll see used more often) or window.OnLoad, etc, because these methods do wait until everything is fully loaded—including tables, images, and videos–before they’ll allow the next portion of code to start.
You can see that immediately after the event listener, there are two functions declared. These won’t do anything at all until they are called. When the DOMContentLoaded event occurs, the init function will be called by the event listener (only once), and the init function contains just a single instruction. That instruction is a setInterval command, and this creates a timer.
The setInterval command accepts two arguments, the first being the action to perform, and the second being the interval in milliseconds. The action will occur at every interval, unless it can’t, in which case the program will usually crash. The action called by our setInterval command is the drawScreen function, and this will occur every 1000 milliseconds.
For now, drawScreen is empty, so executing the script won’t produce any visible result, even though the function will still be called one time per second, which means it will be using some resources on the client computer while it is running.
Add the math stuff
This is simply a short list of variables:
Moving along, we come to hmp which is mpd divided by four. If an end angle of mpd will result in a full circle, then it’s not difficult to see that hmp will give you one radial arc, otherwise known as a quadrant. Typing the number is more efficient than calculating it at runtime.
Next up we have dzn, which is a number representing the percentage of 360 degrees that the hours hand of a clock would need to advance by each time it advances, namely one twelfth, which means 30 degrees (360 x 0.08333333333333333 = 30).
The next number is sxt, which is the percentage of 360 degrees that the minutes hand and the seconds hand need to advance each time they advance, namely one sixtieth, which means 6 degrees (360 x 0.016666666666666666 = 6).
Add the geometry stuff
The next set of values is closely related to the area we are drawing to. To make this code reusable (so it can be used with a canvas of any size), we need to be careful to not hard-code anything that doesn’t need to be hard-coded.
If you know for certain that you’re never going to reuse the code, it’s better to use hard-coded values, however, as anything you can do to reduce the number of calculations will make the program more efficient (with efficiency being very important in computer animation).
The instruction at line 23 sets up an object (cnv) that references the canvas element we created earlier. It’s important not to get mixed up between objects and elements. Line 24 creates a context object (ctx) that is assigned to the canvas object by using the getContext method of cnv.
When drawing the clock, we will need to provide co-ordinates for the position of it. The entire canvas is actually a grid of pixels, with each square on the invisible grid having an address composed of a horizontal and vertical position. The square in the top left corner of the grid is always 0,0 and the square in the bottom right corner is always the maximum width and height values (so in this case it would be 800,450).
The first (horizontal) part of the address of a plot point on a canvas is usually referred to as the x co-ordinate, and the second part of the address is usually referred to as the y co-ordinate. The variables cX and cY are each half of a co-ordinate pair representing the address of the centerpoint of the circle that will be used to draw the clock. cR is the radius of that circle.
The centerpoint of this circle is defined as being half the width and half the height of the canvas, so the address for it will be 400,225. The radius is set to 22% less than half the height of the canvas, just to give the circle a bit of padding from the edge of the canvas, so in this case the radius is 175.5 pixels (225-(225*0.22) = 225 – 49.5 = 175.5).
Add the time stuff
Once we have a Date object, we just fetch the values from it that we need. In this case, that will be hours, minutes, and seconds.
The set of Grad variables are used for calculating the movement of the hands around the clock face. They need to be declared after all the other variables are declared, because they depend on those variables to calculate their values.
Add the drawing stuff
Now for the best part – we get to draw something on the screen. This is the first time that our program will produce any visible results. We’ll do this in two stages, to make it easier to see how it works.
The instruction on line 39 is important. This clears the canvas with every update. If we didn’t do this, we’d see ghost objects left behind from every previous update. Line 40 moves the drawing pen to the position indicated (center of the canvas). The offset you can see included there is taking the aspect ratio into account. Line 41 sets the color and opacity of the line that will be used, and line 42 sets the width of the line in pixels. Line 43 plots the path of the circle on the canvas, and then line 44 actually puts down the ink on the canvas to make the circle visible. This is the result.
To make it a bit more impressive, we should add some hour markers around the face. We start by adding the data to arrays to hold the X and Y values. These arrays will be called nX and nY. Because pinpoint accuracy is not very important in this project, we can round off all the values to the closest integer, like so:
Take especial note of the values at nX(0), nX(6), nY(3), and nY(9). These are the cardinal points on the clock face (0,3,6, and 9), and should be given special treatment to help them stand out.
But where did the X and Y values come from? The X values can be calculated using a formula like this:
Where i is the number (from 0 through 11) of the hour point being tested, r is the radius, and width is the full width of the canvas. The Y values are calculated in a similar way:
These formulas are only necessary if you want to use a canvas of a different size or a circle with a different radius. In code form you could write them as:
a = i * dzn; a = a * mpd; a = a – hmp;
nX=Math.cos(a); nX=nX*xOff; nX=nX+cX;
nY=Math.sin(a); nY=nY*yOff; nY=nY+cY;
The above code snippet assumes that all of the other variables already exist. Why are the offsets needed? It’s to ensure you don’t hit the edges of the canvas with your circle, or part of the circle will go off the canvas and be invisible. Well, that’s all the geeky stuff dealt with. We can get on with the drawing now.
From line 52 through line 60 we have created a loop, which will start with number 0 and end with number 11. We then have a conditional test to see if the i variable has a remainder if divided by 3, which will be true for all the numbers except 0,3,6, and 9 (the cardinal points mentioned earlier).
When there is a remainder, we draw a circle with a radius of 6 pixels, and otherwise a circle with a radius of 9 pixels. After all the circles have been plotted, we use the stroke method to paint them on the canvas.
The result of all that effort looks like this:
That is certainly looking a bit more like a clock now, but it’s missing the most important thing: hands. This is what we’ll take care of next, and of course this is where we’ll be doing the first bit of animation.
Adding the animation stuff
What we’re going to do here is create a single function that can animate all three hands of the clock. Now you know why all that additional math was necessary earlier – without it, we’d have to write code to animate each of the hands individually.
The anim function that we create to do this is going to accept four arguments:
- a grad value, which we defined earlier (hGrad, mGrad, sGrad)
- hndWid – the hand for indicating seconds should be thinner than the other two hands
- hndLen – each of the hands is a different length
- hndCol – the hand for indicating seconds should be a different color to the others
Each of the hands will make two calls to the function. This is necessary because the hands of a clock don’t extend perfectly from the centerpoint towards the edge, they have a small bit of overlap behind them. So we need to draw the main part of the hand plus the bit that sticks out at the back, and they need to be drawn separately (for simplicity) but to move in sync. Here’s how that looks:
The function that these lines are calling is:
The only thing that should need explaining there is cX and cY are the current X and Y points, while fX and fY are the future X and Y points. When we draw a line, we move to cX,cY and then draw the line to fX,fY. And doing that will give us the animated version of the image below:
Those moving parts are vulnerable without protection, so we’ll just throw a bit of glass over the top:
That is all of the animation and drawing done, so now we just need to finish up with playing sound, which is another important HTML5 skill to have.
Adding the reaction stuff
A clock doesn’t react to many things, except it may have an hourly chime or an alarm function. To add an hourly chime to this clock, all we really need to do is detect when m<1 and s<1 (if you only check the minute hand, the sound will play every second for a full minute, instead of just being a brief chime). To make this work, we’ll need to modify the start of our script.
We need to load the sound file before we do the other stuff, because remember there is a timer set. If we load the sound file inside the part of the program that works with the timer, it would load the file every second. Obviously that’s a very bad idea.
First let’s take a look at the changes to the top of the script:
You will need to find some very short MP3 file and name it “chime2.mp3” if you want to test the sound playing ability.
And for the final step, we just need to add our state check and reaction script, like so:
Also sounds on web pages are really controversial. If you don’t need sound, avoid it. If you do need sound, make it opt-in rather than opt-out, and this is as simple as using a single variable linked to a checkbox. Checking to see if that variable is set to on or off before playing the sound will help ensure the website visitors are never annoyed by your script.
As any experienced developer knows, not annoying people is the pinnacle of success in website design and development.
The source code for this project is available here.