Graphics Building the Clock Face

This lesson builds the static clock face-the circular dial and hour tick marks-using arc() and simple trigonometry as the foundation the next two lessons will animate.

Planning the Clock Face

An analog clock is really just three drawings layered on top of each other: a round face, a set of tick marks around the rim, and a pair (or trio) of rotating hands. Building it in canvas is a good way to combine arc(), path drawing, and trigonometry into one recognizable project. This lesson covers the face and ticks; the next lesson adds the hands, and the one after that makes it tick every second.

  • A canvas element sized to fit the clock comfortably
  • An outer circle for the dial, drawn with arc()
  • A small filled circle at the center where the hands will pivot
  • Twelve evenly spaced tick marks, one for each hour
  • A coordinate system centered on the clock so angle math stays simple

Example

<!DOCTYPE html>
<html>
<head>
</head>
<body>

<canvas id="clock" width="300" height="300"></canvas>

<script>
const canvas = document.getElementById('clock');
const ctx = canvas.getContext('2d');

const radius = canvas.height / 2;
ctx.translate(radius, radius);
const clockRadius = radius * 0.9;

function drawFace(ctx, radius) {
  ctx.beginPath();
  ctx.arc(0, 0, radius, 0, 2 * Math.PI);
  ctx.fillStyle = '#f5f5f5';
  ctx.fill();
  ctx.lineWidth = radius * 0.04;
  ctx.strokeStyle = '#333333';
  ctx.stroke();

  ctx.beginPath();
  ctx.arc(0, 0, radius * 0.05, 0, 2 * Math.PI);
  ctx.fillStyle = '#333333';
  ctx.fill();
}

drawFace(ctx, clockRadius);
</script>

</body>
</html>

Notice the ctx.translate(radius, radius) call before anything is drawn. It shifts canvas coordinate (0, 0) to the visual center of the clock, so every shape from here on can be drawn as if the center of the dial were the origin. Without that translate, every arc, tick, and hand would need its own centerX/centerY offset added by hand.

Note: Translate once, right after getting the context, and treat (0, 0) as the clock's center for the rest of the drawing code. It is much easier to reason about a rotating hand at the origin than one offset by an arbitrary center point.

Placing the Hour Ticks with Trigonometry

A full circle is 2*PI radians, and a clock face has 12 hour positions, so each hour is 2*PI / 12, or PI / 6 radians, apart. Looping from 0 to 11 and multiplying the loop index by PI / 6 gives the angle for every tick. Rather than converting that angle into x and y coordinates by hand, it is simpler to rotate the whole canvas context to that angle, draw a short vertical line pointing 'up' (negative y, since canvas y grows downward), then undo the rotation before moving to the next tick.

Example

<!DOCTYPE html>
<html>
<head>
</head>
<body>

<canvas id="clock" width="300" height="300"></canvas>

<script>
const canvas = document.getElementById('clock');
const ctx = canvas.getContext('2d');

const radius = canvas.height / 2;
ctx.translate(radius, radius);
const clockRadius = radius * 0.9;

function drawTicks(ctx, radius) {
  for (let i = 0; i < 12; i++) {
    const angle = i * (Math.PI / 6); // 360 degrees / 12 hours
    ctx.save();
    ctx.rotate(angle);
    ctx.beginPath();
    ctx.lineWidth = radius * 0.03;
    ctx.moveTo(0, -radius * 0.9);
    ctx.lineTo(0, -radius * 0.85);
    ctx.stroke();
    ctx.restore();
  }
}

drawTicks(ctx, clockRadius);
</script>

</body>
</html>
Tick typeCount around dialTypical lengthTypical width
Hour ticks12About 5-8% of the radiusThicker, e.g. 3% of the radius
Minute ticks (optional)60About 2-3% of the radiusThinner, e.g. 1% of the radius
Note: save() and restore() around each tick's rotate() keep the rotations from stacking up. Each iteration starts from the same unrotated state, rotates just enough to reach its own tick, draws, and restores, so tick 5 never inherits the rotation left over from tick 4.