Canvas Clock

What's covered in this tutorial

In this tutorial, we will take a quick look at the HTML <canvas> tag, and step by step, we will create a clock like the one below:

Your browser does not support canvas

We will take a look at

  • How to draw basic shapes
  • How to draw background images on the canvas
  • How to rotate the canvas around it's center point
  • How to add shadow effects to shapes.
  • How to write text on the canvas.

Browser support

Canvas is now more or less supported by all major browsers. Internet Explorer added support for it in IE9. There are some methods, or more precisely arguments to methods which aren't supported by them all, but we won't use any of them in this tutorial.

Step 1: Preparation

Background image

In this tutorial, we are going to draw an image on the canvas. It's this clock face image from http://agf81.deviantart.com/art/Clock-Face-2-169291889. So before we begin, please right click on the image below and save it so that you can access is it from your code later on (example: inside the sub folder "images").

We start our tutorial by creating a file like this in our html editor:

<html>
<head>
   <title>Canvas Clock</title>
</head>
<body>
   <canvas id="myCanvas" width="400" height="400">
    Your browser does not support canvas
  </canvas>
<script>
</script>
</body>
</html>

It contains a canvas element and a Script tag. Notice that the canvas element contains a width and height attribute. This is important.

Usually, we will set width and height of elements by specifying CSS attributes, example: <canvas style="width:400px;height:400px">. But this will not work so well for canvas since it will just scale it and not change it's internal width and height.

This is actually good news, because it means that we can scale canvas elements by changing it's CSS width and height attributes. Try for example to click the link below to resize the clock above to 300x300 pixels.

canvas.style.width = canvas.style.height = '300px'

We have also added a SCRIPT tag. All our code for the clock will go between the <script> and </script> tag.

The first we want to do in our code is to get a Javascript DOM reference to the canvas tag and a reference to the "context" of the canvas tag.

var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

The context object contains all the properties and methods which we will use to draw on the canvas. Example:

context.moveTo(50,50);
context.lineTo(100,100);
context.stroke();
etc...

So now, we should have a file like this:

<html>
<head>
   <title>Canvas Clock</title>
</head>
<body>
   <canvas id="myCanvas" width="400" height="400">
    Your browser does not support canvas
  </canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
</script>
</body>
</html>

We have a reference to the canvas DOM element(<CANVAS>) and a reference to the context.

Operations required to create a clock

So what do we need to do in order to create the clock above?

We will need to do the following:

  • Add the background image
  • Write our own brand name "Canvas" to the watch.
  • Create an hour hand
  • Create a minute hand
  • Create a second hand
  • Have a function which ties everything together, a createClock function
  • And finally have a clockApp function which executes only once when we start the app.

That's basically it. So let's start by creating some empty Javascript functions for each of these:

<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

function addBackgroundImage(){

}

function drawHourHand(){

}

function drawMinuteHand(){

}

function drawSecondHand(){

}

function writeBrandName(){

}

function createClock(){

}

function clockApp(){

}
clockApp(); // Start the application
</script>

Before we do more coding on the clock, let's cover some basic material.

Basics: How to draw on the canvas

We draw on the canvas by using methods on the context object. Examples: moveTo(), lineTo(), rect(), arc() etc.

As an example, if we want to draw a red line from point x=100, y=100 (The top left corner of the canvas is point 0,0). to x=200,y=300, we can use code like this

context.strokeStyle = 'red'; // or alternatively #FF0000
context.beginPath(); // Start a new path
context.moveTo(100,100);
context.lineTo(200,300);
context.stroke(); // Draw a red line

And if we want to fill a rectangle from point x=100,y=150 width a width of 300 and height of 200, we can do it with code like this:

context.fillStyle = 'blue';
context.beginPath();
context.rect(100,150,300,200);
context.fill(); // fill the rectangle with blue color

The context.save() and context.restore() methods

Context properties such as strokeStyle (example color of lines), fillStyle (example: fill color of a rectangle), canvas rotation etc are part of what is called the context state. We can use context.save() to save the current state of the context, and context.restore() to restore back to a previous state.

This example illustrates this:

context.strokeStyle = 'red'; // Set red stroke color
context.save(); // Save current state;

context.strokeStyle = 'blue'; context.beginPath();
context.moveTo(100,100);
context.lineTo(200,100);
context.stroke(); // Draw a blue line

context.restore(); // Restore state back to previous save
// strokeStyle is now "red" and not "blue" since we restored the context.
context.lineTo(200,200);
context.stroke(); // Draws a red line from 200,100 to 200,200

  • First the stroke color was set to red.
  • Then we saved the context state.
  • After that, we updated the context with a blur stroke color
  • Then we created a line from 100,100 to 200,100 with this blue color
  • The call to context.restore() gave us back the old context before last call to context.save(). That means that strokeColor is now "red" again and not "blue".
  • Finally we created a line from our last position(200,100) to 200,200 with the current stroke color, which is red.

Canvas rotation

In our code for the clock we're going to rotate the canvas in order to get the right angle for our hour-, minute-, and second hands. We're going to draw the handles from the centre of the canvas and straight up, and we're going to use canvas rotation to get the right angle. Think of it as if you're drawing on paper and rotates the paper before you start drawing.

Rotation is part of the context state, so after rotating the canvas and drawing the hands, we're going to use context.restore() to restore canvas rotation back to it's previous state, i.e. no rotation.

So let's say the time is 15:45:30 (3:45:30PM), this is what we want to do:

  1. Draw the background image(Clock face) without any rotation of the canvas
  2. Rotate the canvas 90 degrees for the hour hand and then restore the context back to no rotation
  3. Rotate the canvas 270 degrees for the minute hand, and then restore the context back to no rotation
  4. Rotate the canvas 180 degrees for the second hand, and then restore the context back to no rotation.

How to animate

If you think of animations as frames in a movie, each frame is a complete redraw of the canvas. So, in the clock example, each and every second, we need to do all the steps from drawing the background image to drawing the second hand.

Now, let's continue working on the code for our clock.

Step 2: Loading and displaying the background image

The context object has a drawImage() method which we are going to use to place the clock face image on the canvas.

Example of how this method works:

context.drawImage(imageObj, 0,0);

To draw an image at point 0,0 in it's original size, or

context.drawImage(imageObj, 0,0, 400, 400);

To draw an image at point 0,0 with a width of 400 and height of 400.

The first argument to the drawImage method is a reference to an Image object.

Let's create that Image object:

var clockImage = new Image();
clockImage.src = 'images/Clock_Face_2_by_AGF81.png';

The src of this image is the clock face image you downloaded at the start of this tutorial.

Now we can use "clockImage" as first argument to the context.drawImage method.

But before we can do that, we should make sure that the image has been loaded from the server. We do this by adding an "onload" event to the image, and use a variable called "clockImageLoaded" which will be set to true once the image is loaded.

So let's change the code above to this:

var clockImage = new Image();
var clockImageLoaded = false;
clockImage.onload = function(){
   clockImageLoaded = true;
}
clockImage.src = 'images/Clock_Face_2_by_AGF81.png';

Put this block of code right below the line
var context = canvas.getContext('2d');

Now, we will add a test inside clockApp which makes sure that our clock application doesn't start until the background image is loaded.

function clockApp(){
   if(!clockImageLoaded){
      setTimeout('clockApp()', 100);
      return;
   }
  createClock();
}

What this code does is to check if clockImageLoaded is set to true. If it's not, i.e. the clock background image is not yet loaded from the server, it will wait 0,1 seconds before calling the clockApp function again.

Now, let's add the code we need in the addBackground image function.

function addBackgroundImage(){
   context.drawImage(clockImage, 0,0,canvas.width, canvas.height);
}

And make sure that addBackgroundImage() is called from the createClock() function:

function createClock(){
   addBackgroundImage();
}

The context.drawImage method displays the clock face image at coordinate 0,0 and it will scale it to the size of the canvas(i.e. 400x400)

Your entire code should now look like this:

<html>
<head>
<title>Canvas Clock</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400">
  Your browser does not support canvas
</canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

var clockImage = new Image();
var clockImageLoaded = false;
clockImage.onload = function(){
  clockImageLoaded = true;
}
clockImage.src = 'images/Clock_Face_2_by_AGF81.png';

function addBackgroundImage(){
  context.drawImage(clockImage, 0,0,canvas.width, canvas.height);
}

function drawHourHand(){

}

function drawMinuteHand(){

}

function drawSecondHand(){

}

function writeBrandName(){

}

function createClock(){
  addBackgroundImage();
}

function clockApp(){
  if(!clockImageLoaded){
    setTimeout('clockApp()', 100);
    return;
  }
  createClock();
}

clockApp();
  
</script>
</body>
</html>  
  

We mentioned earlier that we want to rotate the canvas when we draw the hour-, minute- and second hands on to the canvas. The center point of rotation is 0x0. Think of it as if you press a finger down on the paper and rotate the paper around the point where your finger is. For our Clock application, we don't want to rotate the clock around the top left corner. We want to rotate it around the center point, i.e. canvas.width/2, canvas.height/2 or 200,200.

Fortunately, the Canvas context object has a method for this called translate. So inside our clockApp function we want to add the line
context.translate(canvas.width/2, canvas.height/2);

Code:

function clockApp(){
  if(!clockImageLoaded){
    setTimeout('clockApp()', 100);
  }
  context.translate(canvas.width/2, canvas.height/2);
  createClock();
}

Not, If you run your page in your web browser, you will see that the background image is positioned at the wrong place. The reason for this is simple. We placed it at point 0x0, but that's no longer at the top left corner of the canvas. It's at the center. So we need to change the drawImage function call to this:

context.drawImage(clockImage, canvas.width/2 * -1 ,canvas.height/2 * -1,canvas.width, canvas.height);

This code is the equivalent to this:

context.drawImage(clockImage, -200 ,-200, 400, 400);

Since 0,0 is at the center of the canvas, -200,-200 is at the top left corner.

Step 3: Drawing the second-, minute- and hour hands

Now, let's start working on displaying time inside the canvas. First let's call drawHourHand, drawMinuteHand and drawSecondHand from the createClock function:

function createClock(){
   addBackgroundImage();
   drawHourHand();
   drawMinuteHand();
   drawSecondHand();
}

These functions also needs to know the time, so let us create a new Date object and pass that date to these functions:

function createClock(){
   addBackgroundImage();

   var theDate = new Date();
   drawHourHand(theDate);
   drawMinuteHand(theDate);
   drawSecondHand(theDate);
}

And also add that theDate as input arguments to the functions:

function drawHourHand(theDate){

}

function drawMinuteHand(theDate){

}

function drawSecondHand(theDate){

}

Now, let's start with the drawSecondHand function.

The first thing we should do is to extract seconds from the input date:

var seconds = theDate.getSeconds();

It will give us a value between 0 and 59.

Now, for the second hand, let's draw a red line from the center of the canvas(0,0) and up to point (0,-150)

We set color by changing context.strokeStyle. Then we start a new path, move the pointer to 0,0 and draw a line up to 0,-150 (Remember that because of context.translate, 0,0 is at the center of the canvas)

function drawSecondHand(theDate){
   var seconds = theDate.getSeconds();

   context.strokeStyle = 'red';
   context.beginPath();
   context.moveTo(0,0);
   context.lineTo(0,-150);
   context.stroke();
}

If you open your page in your browser, you will se something like this:

That looked a little bit boring. Let's spice it up a little bit by creating a shape, Something like this:

We accomplish this by creating a closed path. This path consists of a couple of lines which starts and ends at the same point.

For our drawSecondhand function, it may look like this:

function drawSecondHand(theDate){
   var seconds = theDate.getSeconds();

   context.fillStyle = 'red';
   context.beginPath();
   context.moveTo(0,0); // center
   context.lineTo(-4, -10); // 4 left, 10 up
   context.lineTo(0, -150); // up to 0,-150
   context.lineTo(4,-10); // down to 4 right, 10 up
   context.lineTo(0,0); // back to starting point
   context.fill();
}

Since we want a similar shape for the minute- and hour hand, let's move the shape code into a separate function and add the size as an input parameter(i.e. the -150 value).

This is useful since we want the minute- and hour hand to have a different size than the second hand.

Now, we get this code:

function drawHand(size){
   context.beginPath();
   context.moveTo(0,0); // center
   context.lineTo(-4, -10);
   context.lineTo(0, size * -1);
   context.lineTo(4,-10);
   context.lineTo(0,0);
   context.fill();
}

function drawSecondHand(theDate){
   var seconds = theDate.getSeconds();

   context.fillStyle = 'red';

   drawHand(150);
}

We call drawHand from the drawSecondHand function and send 150 as size argument.

Rotation

Right now, the second hand points upwards. As we mentioned before, we need to rotate the canvas in order to get the correct angle. We will do this by calling the context.rotate() function. The argument to this function is angle in Radians. The formula for degrees to radians is:

Radians = (Math.PI / 180) * degrees

For most of us, it's much easier to think degrees and not radians. So let's create a function which converts degrees to radians:

function degreesToRadians(degrees) {
   return (Math.PI / 180) * degrees
}

And now, let's add rotation to our drawSecondHand function. Since there are 360 degrees in a complete circle and 60 seconds in a minute. We will rotate it seconds * 6 degrees: (60 x 60 = 360)

function drawSecondHand(theDate){
   context.fillStyle = 'red';

   var seconds = theDate.getSeconds();
   context.rotate( degreesToRadians(seconds * 6));

   drawHand(150);
}

After rotation, we want to revert back to no rotation. As mentioned earlier, canvas rotation is part of the Canvas state. So let's use context.save() to save the state before rotation, and context.restore() to restore it back to no rotation afterwards:

function drawSecondHand(theDate){
   context.save();

   context.fillStyle = 'red';

   var seconds = theDate.getSeconds();
   context.rotate( degreesToRadians(seconds * 6));

   drawHand(150);

   context.restore();
}

Now, the second hand is rotated correctly according to time. But it's not updated every second. Let's take care of that. We can do this by calling the createClock function every second. There are two ways of doing this. We can use the setInteval() function or the setTimeout() function.

With setInterval, we change the call to createClock() inside clockApp to this:

function clockApp(){
   if(!clockImageLoaded){
      setTimeout('clockApp()', 100);
      return;
   }
   context.translate(canvas.width/2, canvas.height/2);
   setInterval('createClock()', 1000);
}

The other alternative is to add a setTimeout() to the top of the createClock() function:

function createClock(){
   setTimeout('createClock()',1000);
   addBackgroundImage();

   var theDate = new Date();
   drawHourHand(theDate);
   drawMinuteHand(theDate);
   drawSecondHand(theDate);
}

Which method to use is optional.

If you update your page now, you will see that the second hand is moving.

Your entire code should now look something like this:

<html>
<head>
<title>Canvas Clock</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400">
  Your browser does not support canvas
</canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

var clockImage = new Image();
var clockImageLoaded = false;
clockImage.onload = function(){
  clockImageLoaded = true;
}
clockImage.src = 'images/Clock_Face_2_by_AGF81.png';

function addBackgroundImage(){
  context.drawImage(clockImage, canvas.width/2 * -1 ,canvas.height/2 * -1,canvas.width, canvas.height);
}

function degreesToRadians(degrees) {
  return (Math.PI / 180) * degrees
}

function drawHourHand(theDate){

}

function drawMinuteHand(theDate){

}

function drawHand(size){
  context.beginPath();
  context.moveTo(0,0); // center
  context.lineTo(-4, -10);
  context.lineTo(0, size * -1);
  context.lineTo(4,-10);
  context.lineTo(0,0);
  context.fill();
}

function drawSecondHand(theDate){
  var seconds = theDate.getSeconds();

  context.save();
  context.fillStyle = 'red';

  context.rotate( degreesToRadians(seconds * 6));

  drawHand(150);

  context.restore();
}

function writeBrandName(){

}

function createClock(){

  addBackgroundImage();

  var theDate = new Date();
  drawHourHand(theDate);
  drawMinuteHand(theDate);
  drawSecondHand(theDate);
}

function clockApp(){
  if(!clockImageLoaded){
    setTimeout('clockApp()', 100);
    return;
  }
  context.translate(canvas.width/2, canvas.height/2);
  createClock();
  setInterval('createClock()', 1000)
}

clockApp();
  
</script>
</body>
</html>

Minute hand

Now, let's move on to the minute hand. We follow almost the same procedure as with the second hand. We extract minutes from the "theDate" object by calling the getMinutes() function.

But in order to let the minute hand move smoothly from minute to minute, we also want to add seconds / 60. Like this:

function drawMinuteHand(theDate){
   var minutes = theDate.getMinutes() + theDate.getSeconds() / 60;
}

So when the time is 15:51:30, the minute hand should be positioned right between minute 51 and minute 52.

The rest of the code for this function is almost identical to the code for the second hand. The only thing we change is

  • The color of the hand, which we set to black,
  • rotation, which be according to minutes instead of second and
  • size, which we set to 130 instead of 150.

This will give us this code for the drawMinuteHand() function:

function drawMinuteHand(theDate){
   var minutes = theDate.getMinutes() + theDate.getSeconds() / 60;

   context.save();
   context.fillStyle = 'black';

   context.rotate( degreesToRadians(minutes * 6));

   drawHand(130);

   context.restore();
}

If you refresh your page now, you will see that we have a black minute hand and a red second hand.

But what if we want to make the minute hand thicker than the second hand. I would like it to look something like this:

We can accomplish this by changing the drawHand function a little bit. Right now, it looks like this:

function drawHand(size){
   context.beginPath();
   context.moveTo(0,0); // center
   context.lineTo(-4, -10);
   context.lineTo(0, size * -1);
   context.lineTo(4,-10);
   context.lineTo(0,0);
   context.fill();
}

I want to make the -4 on line 3, and 4 on line 6 dynamic, since that's the value which controls the thickness ot the hand.

So let's add a new input parameter called thickness. Like this:

function drawHand(size, thickness){
   thickness = thickness || 4;

   context.beginPath();
   context.moveTo(0,0); // center
   context.lineTo(thickness *-1, -10);
   context.lineTo(0, size * -1);
   context.lineTo(thickness,-10);
   context.lineTo(0,0);
   context.fill();
}

Here, we have changed -4 to thickness *-1 and 4 to thickness. We have also added thickness = thickness || 4 to give it a default value of 4 in case no thickness is sent to the function

Now, in the drawMinuteHand function, change drawHand(130); to drawHand(130, 7);

The minute hand on the canvas will now look thicker than the second hand.

Hour hand

Let us complete our clock by adding code to our drawHourHand() function.

First we extract hours in the same way we did for minutes:

function drawHourHand(theDate){
   var hours = theDate.getHours() + theDate.getMinutes() / 60;
}

We set hours to theDate.getHours() + theDate.getMinutes()/60. So when the time is 11:59, the hour hand will be positioned close to 12.

Now, let's figure out how much we need to rotate the canvas. Here it's easy to go wrong.

Hours will have a value between 0 and 24, but in 24 hours the hour hand will have completed two round around the clock. So when the time is 3am (3:00), we want to rotate the canvas 90 degrees, and when the time is 3pm or 15:00, we also want to rotate the canvas 90 degrees.

The formula for this can be:

var degrees = (hours * 360 / 12) % 360;

There is 360 degrees in a complete circle, and twelve hours in a complete circle.

So the time 3:00 gives us 3 * 360 /12 = 90, and 15:00 gives us 15 * 360 /12 = 450, but after using the modulus operator % on 360, it gives us 90. (360+90 = 450 and 0+90 = 90).

You may also drop the % operator, since it's perfectly ok to rotate the canvas 450 degrees.

var degrees = (hours * 360 / 12);

works just as well.

We complete the rest of the code for drawHourHand the same way as before:

function drawHourHand(theDate){
   var hours = theDate.getHours() + theDate.getMinutes() / 60;
   var degrees = (hours * 360 / 12) % 360;

   context.save();
   context.fillStyle = 'black';

   context.rotate( degreesToRadians(degrees));

   drawHand(110, 7);

   context.restore();
}

Step 4: Add brand name

Right above the center of the canvas, we want to write "Canvas" (or a name of your choice) as brand name for our clock.

First, let's make sure that the writeBrandName() function is called from createClock(). We want to call it right after we have added the background image, since we want "Canvas" to be written on top of the background, but appear underneath the hands of the clock.

function createClock(){

   addBackgroundImage();
   writeBrandName();

   var theDate = new Date();
   drawHourHand(theDate);
   drawMinuteHand(theDate);
   drawSecondHand(theDate);
}

The properties and methods we are going to use to write the text is context.font, context.measureText() and context.strokeText(). There's a lot of other options available, which you can take a look at by following the references listed at the end of this tutorial.

First let's specify a font with context.font. context.font let's you specify a lot of font properties such as size, which font to use, font weight(example: bold), font style(example: italic) etc.

We are going to write the brand name in a 25pt size Helvetica font (It's important to choose a font which is available on most computers).

So the context.font code will look like this:

context.font = "25pt Helvetica";

We want the brand name to be center aligned horizontally. There are several ways to do this, but the safest way is to measure the size of the text first, and the subtract half it's width from the x coordinate before we draw the text on to the canvas.

So, let's put our brand name into a brandName variable and call context.measureText to measure it:

var brandName = 'Canvas';
var brandNameSize = context.measureText(brandName);

context.measureText() will return width and height of text according to the current font properties. So it's important that we specify which font to use(context.font=...) before we call measureText.

Remember that 0,0 is still at the center of the canvas. We want to brand name above the center, so a good coordinate for y is -40. For x, the correct coordinate is
0 - width of text / 2.

This is the code for this:

context.strokeText("Canvas", 0 - brandNameSize.width / 2,-40);

context.strokeText() outlines the text. If you want it filled, you can use context.fillText(). The color used will be the current values of strokeStyle and fillStyle.

This is our final code for the writeBranName function:

function writeBrandName(){
   context.font = "25pt Helvetica";
   var brandName = 'Canvas';
   var brandNameSize = context.measureText(brandName);
   context.strokeText("Canvas", 0 - brandNameSize.width / 2,-40);
}

Step 5: Add shadow effects.

Let's finish our tutorial by adding some shadow effects. We want to add a small shadow effect to the hour hand since it's closest to the clock, a little more shadow to the minute hand and most shadow the second hand since it's closest to us.

For shadows, we have these available properties to work with:

  • context.shadowColor : The color of the shadow
  • context.shadowBlur: The size of the shadow blur effect
  • context.shadowOffsetX : How far the shadow should extend from the object in the X direction.
  • context.shadowOffsetY : How far the shadow should extend from the object in the Y direction.

The drawHand() function is used to draw the hour-, minute- and second hand, so let's add the shadow effect there. We want shadowOffsetX and shadowOffsetY to be dynamic. We do that by adding a shadowOffset parameter to the drawHand function:

function drawHand(size, thickness, shadowOffset){

Let's finish by adding the code needed for the shadow effect to this function:

function drawHand(size, thickness, shadowOffset){
   thickness = thickness || 4;

   context.shadowColor = '#555';
   context.shadowBlur = 10;
   context.shadowOffsetX = shadowOffset;
   context.shadowOffsetY = shadowOffset;

   context.beginPath();
   context.moveTo(0,0); // center
   context.lineTo(thickness *-1, -10);
   context.lineTo(0, size * -1);
   context.lineTo(thickness,-10);
   context.lineTo(0,0);
   context.fill();
}

Summary

This completes this intro tutorial to HTML Canvas. We have looked at rotation, simple shapes, shadows and background images. If you want to play a little more with the code, you can for example try to :

  • Add transparency to the second hand. This can be done by changing the context.globalAlpha property to a value between 0 (full transparency) and 1 (full opacity). Example: add context.globalAlpha = 0.8; right below the line context.fillStyle = 'red'.
  • You may also want to try have a combination of fill and stroke for the hands. Simply add context.stroke() at the bottom of the drawHand() function and set context.strokeStyle to a color inside the drawSecondHand(), drawMinuteHand() and drawHour() functions.

My final code look like this: <html>
<head>
<title>Canvas Clock</title>
</head>
<body>
<canvas id="myCanvas" width="400" height="400">
  Your browser does not support canvas
</canvas>
<script>
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');

var clockImage = new Image();
var clockImageLoaded = false;
clockImage.onload = function(){
  clockImageLoaded = true;
}
clockImage.src = 'images/Clock_Face_2_by_AGF81.png';

function addBackgroundImage(){
  context.drawImage(clockImage, canvas.width/2 * -1 ,canvas.height/2 * -1,canvas.width, canvas.height);
}

function degreesToRadians(degrees) {
  return (Math.PI / 180) * degrees
}

function drawHourHand(theDate){
  var hours = theDate.getHours() + theDate.getMinutes() / 60;

  var degrees = (hours * 360 / 12) % 360;

  context.save();
  context.fillStyle = 'black';
  context.strokeStyle = '#555';
  
  context.rotate( degreesToRadians(degrees));

  drawHand(110, 7, 3);

  context.restore();

}

function drawMinuteHand(theDate){
  var minutes = theDate.getMinutes() + theDate.getSeconds() / 60;

  context.save();
  context.fillStyle = 'black';
  context.strokeStyle = '#555';
  context.rotate( degreesToRadians(minutes * 6));

  drawHand(130, 7, 5);

  context.restore();
}

function drawHand(size, thickness, shadowOffset){
  thickness = thickness || 4;

  context.shadowColor = '#555';
  context.shadowBlur = 10;
  context.shadowOffsetX = shadowOffset;
  context.shadowOffsetY = shadowOffset;
  
  context.beginPath();
  context.moveTo(0,0); // center
  context.lineTo(thickness *-1, -10);
  context.lineTo(0, size * -1);
  context.lineTo(thickness,-10);
  context.lineTo(0,0);
  context.fill();
  context.stroke();
}

function drawSecondHand(theDate){
  var seconds = theDate.getSeconds();

  context.save();
  context.fillStyle = 'red';
  context.globalAlpha = 0.8;
  context.rotate( degreesToRadians(seconds * 6));

  drawHand(150, 4, 8);

  context.restore();
}

function writeBrandName(){
   context.font = "25pt Helvetica";
   var brandName = 'Canvas';
   var brandNameSize = context.measureText(brandName);
   context.strokeText("Canvas", 0 - brandNameSize.width / 2,-40);
}

function createClock(){

  addBackgroundImage();
  writeBrandName();

  var theDate = new Date();
  drawHourHand(theDate);
  drawMinuteHand(theDate);
  drawSecondHand(theDate);
}

function clockApp(){
  if(!clockImageLoaded){
    setTimeout('clockApp()', 100);
    return;
  }
  context.translate(canvas.width/2, canvas.height/2);
  createClock();
  setInterval('createClock()', 1000)
}

clockApp();
  
</script>
</body>
</html>  

Please, let me know what you think about this tutorial, and what you would like to see next.

Exercises for the adventurous:

Here are some exercises which you can try:

  1. Add a logo image above the brand name
  2. Draw a circle around the watch.

References

 

Comments

Chris Bolson
Great tutorial Alf,
I have just followed it through step by step and, needless to say, it works.
I have been wanting to have a go at doing some canvas animation and I think that this might have started the ball rolling :)
Chris Bolson at 01:55PM, 2011/11/01.
Admin comment
DHTMLGoodies
Thanks Chris,

Yes, creating canvas animations can be very fun once you gets the hang of it.

I've added a link to a new tutorial above, which is quite good to start with:

http://www.html5canvastutorials.com/tutorials/html5-canvas-tutorials-introduction/

DHTMLGoodies at 02:50PM, 2011/11/01.
Chris Bolson
Thanks, I will take a look.
By the way, those last 2 links are broken - you have an double quote mark at the end of each of them (or rather you are missing the opening quote marks on the href ) ;)
Chris Bolson at 02:57PM, 2011/11/01.
Protus Goodluck Fortunatus
For real the lesson have made me to fill as if am at home since i had gone to the class you had when doing the javascript class. you have real made my life more easy and more simpler. Thanks alot.
Protus Goodluck Fortunatus at 05:18AM, 2011/11/10.
Paul Tate
Great tutorial - made it easy for a part time code cutter like me to follow, thanks.

I made a plugin for FlatPress using your code, and I gave you a link back to this page.

http://flatpress.georgi.co.uk/2011/11/24/canvas-clock-plugin/

You can also see it on the FlatPress Wiki plugins page, with a few pictures of some other clock designs I made:

http://wiki.flatpress.org/res:plugins#canvas_clock

I just have one problem: when I view the clock in FireFox (yours as well as my plugin version) I get a TWO seconds hands (but one shadow). The "ghost" seconds hand is one second behind the first one and appears when the real seconds hand appears when you first load the page, i.e. it all looks fine but when that first second ticks over the outline (no fill colour) of the seconds hand remains and proceeds to follow the coloured in version around the clock.

No-one's reported this to me - I've just noticed it myself (Firefox isn't my main browser - it looks fine in Opera & Chrome).

I just did a screen shot of it and posted it here so you can see what I mean:

http://flatpress.georgi.co.uk/canvasclockff3/

Any ideas what's going on?
Paul Tate at 12:15PM, 2011/12/26.
Paul Tate
Sorry, I got my email address wrong on my previous comment (in case you wanted to discuss/troubleshoot the problem I'm having outside of this page)
Paul Tate at 12:32PM, 2011/12/26.
Paul Tate
I just tested it on Version 8 on another computer and it looks fine. Not being a FF user all I can say is "Wow! How did they get from versions 3/4 right up to those high numbers?". Pretty confusing to me - they seem to have a few different versions running concurrently from what I can tell from the list at Wikipedia.
Paul Tate at 02:22PM, 2011/12/26.
Jazz
Great script. I was wondering, is there a way to call the time from a server so I can display multiple clocks from different time zones?
Jazz at 10:50PM, 2012/02/12.
Louis Opperman
Thank you for a great canvas animation tutorial!
Louis Opperman at 07:57AM, 2012/03/13.
Arindam Mojumder
I don't like one thing in this tutorials and that is drawing object in each second. Is there no other options of rotating object by drawing it only one time? Is this a good OOps concept? I think repetition of same object drawing will effect on the system memory. Sorry if I am wrong. I am new to java-script platform.
Arindam Mojumder at 07:15AM, 2012/06/05.
Admin comment
DHTMLGoodies
Arindam,

That's a good question since the standard behaviour of DHTML is that you can move objects. An alternative to Canvas is SVG(Scalable Vector Graphic) which let's manipulate objects after they have been rendered.

However, when working with canvas, you have to think more in terms of a movie/film where you show a number of frames per second. With the clock, I only need to show one frame per second, so I'm re-painting the screen every second.

Canvas does not support moving objects once they have been painted to the screen.

DHTMLGoodies at 05:03AM, 2012/06/06.
Francis Cagney
Excellent tutorial. Its so good I'm going to make two "trivial" criticisms. I'm an embedded programmer where waste is always a bad idea. I'm serious, it is a great canvas lesson so I'm paying it the compliment of criticising what stops it being perfect!

Instead of drawing the image on the canvas I would have had the image as a static HTML element, and only used the canvas for drawing the hands. This would have reduced the "work" done each second.

Polling to see if the image is loaded is clumsy. The image.onload event should cause the first rendering, and start the 1 second timer.

I actually came here to solve a problem that I don't think is stated clearly here. If you want to resize the canvas you must change both the canvas.width/height to an integer, and canvas.style.width/height to integer+"px".
Francis Cagney at 04:37AM, 2012/07/02.
Eph
Hello,

Great tutorial!

Could you recommend a similar tutorial that would also include the alarm clock function?

It could be an online tutorial or a book.

Thanks :)
Eph at 09:09AM, 2012/08/31.
eburlea
Hi,

Very useful and easy to follow. Thank you!

It would be interesting to add sound effect when the second hand is moving.

Regards!
eburlea at 03:00AM, 2013/02/27.
Aizure
I love it...And I firmly believe that we must appreciate how much effort he has done to achieve it. Above all he is teaching and allowing everyone else to learn from it....He did a wonderful job... So guys always appreciate what is done than to criticize what is not done...Enjoy life guys...Regardless of your opinions.
Aizure at 01:12PM, 2013/05/09.
md khurshid
Sir i Love your tutorial, and i want to learn from you can you do it
md khurshid at 01:08AM, 2013/07/15.
Ernesto Chapa
Hey, this tutorial is awesome. However, my clock is not showing on my browser. Any suggestions on what could it be?
Ernesto Chapa at 01:29PM, 2013/07/25.
milen
Nice post! If someone get lazy to make the work done, can byu fully costomizable version from here. http://bit.ly/html5-canvas-clock
milen at 07:27AM, 2014/06/02.
Amit Kumar
<div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div><div id="timer">2:00</div>
Amit Kumar at 12:32AM, 2014/07/24.
Lucia
<a href="#comment1348">Jazz wrote:</a>
Great script. I was wondering, is there a way to call the time from a server so I can display multiple clocks from different time zones?


I'm also interested in this answer! Does anybody how to do it?
Great tutorial btw.
Thanks !
Lucia at 02:38AM, 2014/09/04.
Mark

I replaced 'context.lineTo(0, size * -1);' in the drawHand function with the lines

context.lineTo(-1, size * -1);
context.lineTo(1, size * -1);

It improves the visibility of the second hand on a display 360 pixels wide.
Mark at 03:14AM, 2015/04/09.

Post your comment

Don't have an avatar? Create one at Gravatar.com.

Confirmation code:


About/Contact | A good idea? | Submit a script | Privacy notice
© 2005 - 2010 dhtmlgoodies.com | post@dhtmlgoodies.com