Clock Hands
Modern Canvas Clock: Drawing the Hands
Section titled “Modern Canvas Clock: Drawing the Hands”Part IV: Drawing the Clock Hands
Section titled “Part IV: Drawing the 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();}Code Explained
Section titled “Code Explained”-
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. -
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.PIradians. - Hour Hand:
(hour * Math.PI / 6): There are 12 hours, so each hour is(2 * Math.PI) / 12 = Math.PI / 6radians. 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 / 30radians. Fractional part for seconds is added. - Second Hand:
(second * Math.PI / 30): Similar to the minute hand, each second isMath.PI / 30radians.
- A full circle is
- Calls
drawHand()for each hand, providing the calculated angle, length (relative toclockRadius), and width (also relative toclockRadius).
- Gets the current time using
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 movementconst hourAngle = (hour * Math.PI / 6) + (minute * Math.PI / (6 * 60)) + (second * Math.PI / (360 * 60));drawHand(ctx, hourAngle, radius * 0.5, radius * 0.07);drawHand(context, positionAngle, length, lineWidth):context.beginPath(): Starts a new drawing path for the hand.context.lineWidthandcontext.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 bypositionAngle. 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 usingcontext.save()before transforming andcontext.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 centerradius *= 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 lessonfunction 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 functionfunction 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);