Skip to content

Clock Hands

With the clock face and numbers in place, the final step is to draw the hands: hour, minute, and second. We’ll create JavaScript functions to calculate their positions based on the current time and render them on the canvas.

The main drawClock function will now orchestrate drawing the face, numbers, and the time. The drawTime function will get the current time and call drawHand for each hand, after calculating its angle. The drawHand function is a utility to draw a line of a specific length and width, representing a clock hand.

// Assuming drawFace(ctx, radius) and drawNumbers(ctx, radius) are defined from previous parts.
function drawClock(ctx, radius) {
// Clear the canvas for redrawing (important for animation, good practice for static too)
ctx.clearRect(-radius, -radius, canvas.width, canvas.height);
drawFace(ctx, radius);
drawNumbers(ctx, radius);
drawTime(ctx, radius);
}
function drawTime(context, clockRadius) {
const now = new Date();
let hour = now.getHours();
let minute = now.getMinutes();
let second = now.getSeconds();
// Calculate angle for hour hand
hour = hour % 12; // Convert to 12-hour format
// The hour hand moves fractionally based on minutes and seconds
const hourAngle = (hour * Math.PI / 6) +
(minute * Math.PI / (6 * 60)) +
(second * Math.PI / (360 * 60));
drawHand(context, hourAngle, clockRadius * 0.5, clockRadius * 0.07);
// Calculate angle for minute hand
// The minute hand moves fractionally based on seconds
const minuteAngle = (minute * Math.PI / 30) +
(second * Math.PI / (30 * 60));
drawHand(context, minuteAngle, clockRadius * 0.8, clockRadius * 0.07);
// Calculate angle for second hand
const secondAngle = (second * Math.PI / 30);
drawHand(context, secondAngle, clockRadius * 0.9, clockRadius * 0.02);
}
function drawHand(context, positionAngle, length, lineWidth) {
context.beginPath();
context.lineWidth = lineWidth;
context.lineCap = "round"; // Makes the hand tips rounded
context.moveTo(0, 0); // Start drawing from the center of the clock
// Rotate the canvas to the hand's angle
// Note: 0 angle is 3 o'clock. To make 0 angle 12 o'clock, we'd typically subtract Math.PI / 2.
// However, our calculations map directly: angle 0 for 12h, PI/2 for 3h, PI for 6h, 3PI/2 for 9h.
// And lineTo(0, -length) draws upwards in the rotated context.
// So, positive angle rotates counter-clockwise. lineTo(0, -length) draws 'up' in the new orientation.
context.rotate(positionAngle);
context.lineTo(0, -length); // Draw the hand pointing upwards in the rotated context
context.stroke(); // Render the hand
// Rotate the canvas back to its original orientation
context.rotate(-positionAngle);
// Alternative using save() and restore():
// context.save();
// context.rotate(positionAngle);
// context.beginPath();
// context.moveTo(0,0);
// context.lineTo(0, -length);
// context.lineWidth = lineWidth;
// context.lineCap = "round";
// context.stroke();
// context.restore();
}
  1. drawClock(ctx, radius): This function is now the main drawing routine. Before drawing, ctx.clearRect() can be used to clear the previous frame, which is essential for animation. For a static clock, it ensures a clean draw if the function is called multiple times.

  2. drawTime(context, clockRadius):

    • Gets the current time using new Date().
    • Converts hours to 12-hour format (hour % 12).
    • Calculates the angle for each hand. The angles are in radians:
      • A full circle is 2 * Math.PI radians.
      • Hour Hand: (hour * Math.PI / 6): There are 12 hours, so each hour is (2 * Math.PI) / 12 = Math.PI / 6 radians. Fractional parts are added for minutes and seconds to ensure smooth movement.
      • Minute Hand: (minute * Math.PI / 30): There are 60 minutes, so each minute is (2 * Math.PI) / 60 = Math.PI / 30 radians. Fractional part for seconds is added.
      • Second Hand: (second * Math.PI / 30): Similar to the minute hand, each second is Math.PI / 30 radians.
    • Calls drawHand() for each hand, providing the calculated angle, length (relative to clockRadius), and width (also relative to clockRadius).
const now = new Date();
let hour = now.getHours();
let minute = now.getMinutes();
let second = now.getSeconds();
hour = hour % 12;
// Example: Hour hand calculation includes minute and second fractions for smooth movement
const hourAngle = (hour * Math.PI / 6) +
(minute * Math.PI / (6 * 60)) +
(second * Math.PI / (360 * 60));
drawHand(ctx, hourAngle, radius * 0.5, radius * 0.07);
  1. drawHand(context, positionAngle, length, lineWidth):
    • context.beginPath(): Starts a new drawing path for the hand.
    • context.lineWidth and context.lineCap = "round": Set the thickness and style of the hand’s line.
    • context.moveTo(0, 0): Sets the starting point of the line to the center of the clock (which is (0,0) in our translated coordinate system).
    • context.rotate(positionAngle): Rotates the entire canvas coordinate system by positionAngle. Subsequent drawing operations will be relative to this new orientation.
    • context.lineTo(0, -length): Draws a line from the current point (0,0) to (0, -length). In the rotated system, this effectively draws the hand upwards along the new y-axis.
    • context.stroke(): Renders the line on the canvas.
    • context.rotate(-positionAngle): Rotates the canvas back by the same angle. This is crucial so that the next hand (or other drawing operations) starts from the original, unrotated orientation. An alternative and often preferred method for isolating transformations is using context.save() before transforming and context.restore() afterwards.

This completes the static drawing of an analog clock. To make it a live clock, you would typically use setInterval() or requestAnimationFrame() to call drawClock() every second. You can learn more about JavaScript animations here.

// Full example script for this part:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
let radius = canvas.height / 2;
ctx.translate(radius, radius); // Move origin to center
radius *= 0.90; // Apply padding
// Assume drawFace and drawNumbers are defined (copied from previous parts for standalone example)
function drawFace(context, r) {
context.beginPath();
context.arc(0, 0, r, 0, 2 * Math.PI);
context.fillStyle = 'white';
context.fill();
// Clock border
context.strokeStyle = '#333';
context.lineWidth = r * 0.1;
context.stroke();
// Center dot
context.beginPath();
context.arc(0, 0, r * 0.05, 0, 2 * Math.PI);
context.fillStyle = '#333';
context.fill();
}
function drawNumbers(context, r) {
context.font = `${r * 0.15}px Arial`;
context.textBaseline = "middle";
context.textAlign = "center";
for (let num = 1; num <= 12; num++) {
const angle = num * Math.PI / 6;
context.rotate(angle);
context.translate(0, -r * 0.85);
context.rotate(-angle); // Rotate back to draw number upright
context.fillText(num.toString(), 0, 0);
context.rotate(angle); // Rotate to original position for this number
context.translate(0, r * 0.85); // Translate back to center
context.rotate(-angle); // Rotate back system to 0 for next number
}
}
// Functions from this lesson
function drawTime(context, clockRadius) {
const now = new Date();
let hour = now.getHours();
let minute = now.getMinutes();
let second = now.getSeconds();
hour = hour % 12;
const hourAngle = (hour * Math.PI / 6) +
(minute * Math.PI / (6 * 60)) +
(second * Math.PI / (360 * 60));
drawHand(context, hourAngle, clockRadius * 0.5, clockRadius * 0.07);
const minuteAngle = (minute * Math.PI / 30) +
(second * Math.PI / (30 * 60));
drawHand(context, minuteAngle, clockRadius * 0.8, clockRadius * 0.07);
const secondAngle = (second * Math.PI / 30);
drawHand(context, secondAngle, clockRadius * 0.9, clockRadius * 0.02, 'red'); // Optional: color for second hand
}
function drawHand(context, positionAngle, length, lineWidth, color = '#333') {
context.beginPath();
context.lineWidth = lineWidth;
context.lineCap = "round";
context.strokeStyle = color; // Allow hand color customization
context.moveTo(0, 0);
context.rotate(positionAngle);
context.lineTo(0, -length);
context.stroke();
context.rotate(-positionAngle);
context.strokeStyle = '#333'; // Reset strokeStyle if changed
}
// Main drawing function
function drawClock() {
// Clear previous frame (optional for static, essential for animation)
// ctx.clearRect(-radius - ctx.lineWidth, -radius - ctx.lineWidth, canvas.width, canvas.height);
// For a full clear considering the canvas was translated:
ctx.save();
ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transform to default
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.restore(); // Restore transform with origin at center
drawFace(ctx, radius);
drawNumbers(ctx, radius);
drawTime(ctx, radius);
}
drawClock(); // Draw the clock once
// For a live clock, update every second:
// setInterval(() => drawClock(), 1000);
// Or using requestAnimationFrame for smoother browser animations:
// function runClock() {
// drawClock();
// requestAnimationFrame(runClock);
// }
// requestAnimationFrame(runClock);