Project: Analog Clock
The problem
Draw a working clock: 60 tick marks around a dial, plus hour, minute, and second hands that show the real time. Every hand is an angle problem — and screens measure angles differently than math class.
Angles on a screen
Math angles start at +x and go counterclockwise with y up. A clock starts at 12 and goes clockwise with y down. For a hand showing a fraction \(f\) of a full turn (e.g. 15 minutes = 0.25), the clockwise-from-top angle is:
and the endpoint at length \(L\) from center \((c_x, c_y)\) is:
Check: at \(f = 0\) (12 o’clock), \((x, y) = (c_x, c_y - L)\) — straight up. At \(f = 0.25\), \((c_x + L, c_y)\) — pointing right. Correct.
Time → fractions
Including the smaller unit makes hands glide instead of jumping (the minute hand creeps as seconds pass, like a real clock).
Full build
Paste this into the JS Playground in canvas mode:
var cx = W / 2, cy = H / 2, R = 150;
// Dial + 60 ticks
ctx.strokeStyle = '#e2e8f0';
ctx.lineWidth = 4;
ctx.beginPath(); ctx.arc(cx, cy, R, 0, Math.PI * 2); ctx.stroke();
for (var i = 0; i < 60; i++) {
var a = (i / 60) * Math.PI * 2;
var big = (i % 5 === 0);
var r1 = R - (big ? 18 : 9);
ctx.lineWidth = big ? 4 : 2;
ctx.beginPath();
ctx.moveTo(cx + r1 * Math.sin(a), cy - r1 * Math.cos(a));
ctx.lineTo(cx + R * Math.sin(a), cy - R * Math.cos(a));
ctx.stroke();
}
function hand(fraction, length, width, color) {
var a = fraction * Math.PI * 2;
ctx.strokeStyle = color;
ctx.lineWidth = width;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.lineTo(cx + length * Math.sin(a), cy - length * Math.cos(a));
ctx.stroke();
}
loop(function () {
var now = new Date();
clear();
// (redraw dial each frame — wrap the code above in a function in real code)
hand(((now.getHours() % 12) + now.getMinutes() / 60) / 12, R * 0.55, 7, '#e2e8f0');
hand((now.getMinutes() + now.getSeconds() / 60) / 60, R * 0.8, 5, '#38bdf8');
hand(now.getSeconds() / 60, R * 0.9, 2, '#f472b6');
});The snippet redraws hands each frame on top of a static dial. For a tidy program, wrap the dial drawing in
drawDial()and callclear(); drawDial();inside the loop.
Try it
- Add a smooth second hand: use
s + ms/1000over 60 instead of whole seconds. - Add numbers: place 12, 3, 6, 9 with
fillTextat \(f = 0, 0.25, 0.5, 0.75\). - Break it on purpose: use \(x = c_x + L\cos\theta\) instead of sine. Which direction does 12 o’clock point now, and why?
Next: Project: Projectile Lab — parametric motion and the 45° max-range rule.