jasonkarldavis.com

SVG Clock: The Nitty Gritty

First thing is first, let's draw our clock! This includes the main circle, tick marks, and the hands. All of these are made easy enough to do with <circle/> and <line/> elements that SVG has. However, of some mathematical interest is how to precisely draw the tick marks.

For this, let's use one of precalc's best subjects, polar coordinates. Without going into too much detail, in order for this to work we need to change the origin from (0, 0) to (100, 100) (our clock will be 200px wide). Another doozy that I didn't think at first of was rotating the entire thing -90°, but is crucial into not being 3 hours, 15 minutes, and 15 seconds off. A clock starts at 270°, or -90°, while the typical coordinate axis system starts at 0°. Luckily for us, SVG pulls through with flying colors with the transform attribute. Let's look at our document so far:

<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/css" href="clock.css"?>		
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" class="clock">
	<g transform="translate(100,100) rotate(-90)">
		<!-- clock face here -->
	</g>
</svg>

We'll leave the coloring and such up to our stylesheet, which we won't really get into. Now, onto drawing the circle:

<circle r="100"/>

SVG assumes x=0 and y=0 if left out, so all we needed to do was tell it the radius. Now, here comes the fun part. A tick mark is basically a line. If we were to look at the x and y coordinates of the endpoints, both would more often than not be some irrational mess.

If we look at them as a radius and an angle, suddenly everything is clean. For each tick mark, the angle is the same. The radius, however, changes a few pixels. If we were to make the tick marks 10px long, the radius changes by that much. As we all should know from precalc, (r, θ) converts to (r cos θ, r sin θ).

What is θ though? We are creating a total of 12 tick marks. 360°/12 = 30°. Each tick mark increments 30°. Now we just plug and chug. The first line's x1 is 100*cos 0, or 100. Its y1 is 100*sin 0, or 0. x2 is 90*cos 0, and y2 is 90*sin 0. Plugging in all of the values, and rounding to the nearest integer for convenience's sake, we have:

<line x1="100"  y1="0"    x2="90"   y2="0"  />
<line x1="87"   y1="50"   x2="78"   y2="45" />
<line x1="50"   y1="87"   x2="45"   y2="78" />
<line x1="0"    y1="100"  x2="0"    y2="90" />
<line x1="-50"  y1="87"   x2="-45"  y2="78" />
<line x1="-87"  y1="50"   x2="-78"  y2="45" />
<line x1="-100" y1="0"    x2="-90"  y2="0"  />
<line x1="-87"  y1="-50"  x2="-78"  y2="-45"/>
<line x1="-50"  y1="-87"  x2="-45"  y2="-78"/>
<line x1="0"    y1="-100" x2="0"    y2="-90"/>
<line x1="50"   y1="-87"  x2="45"   y2="-78"/>
<line x1="87"   y1="-50"  x2="78"   y2="-45"/>

With the tick marks gone, it is just a matter of the second, minute, and hour hands. Because they aren't moving yet, we'll simply place them at 3:15:15. The hands I used:

<line id="hourhand"   x1="0" y1="0" x2="0" y2="40"/>
<line id="minutehand" x1="0" y1="0" x2="0" y2="65"/>
<line id="secondhand" x1="0" y1="0" x2="0" y2="90"/>

Hurray, we can animate them now! We've come this far, now believe it or not, the animation is the easiest part. Since we're all masters of polar coordinates now, we simply need to obtain the appropriate angle for each and at any given time. 360 degrees, 60 seconds... hey, 6 degrees per second sounds about right! As well as 6 degrees per minute, and 30 degrees per hour. The one quick "gotcha" here is that Javascript's Math.sin and Math.cos take arguments in radians, not degrees. No biggie, we just multiply by π/180°. The code:

var sHand = document.getElementById('secondhand');
var mHand = document.getElementById('minutehand');
var hHand = document.getElementById('hourhand');

function updateSeconds() {
	var angle = (new Date()).getSeconds() * Math.PI/30;

	if (angle == 0)
		updateMinutes();

	sHand.x2.baseVal.value = 90 * Math.cos(angle);
	sHand.y2.baseVal.value = 90 * Math.sin(angle);
}

function updateMinutes() {
	var angle = (new Date()).getMinutes() * Math.PI/30;

	if (angle == 0)
		updateHours();

	mHand.x2.baseVal.value = 65 * Math.cos(angle);
	mHand.y2.baseVal.value = 65 * Math.sin(angle);
}

function updateHours() {
	var angle = (new Date()).getHours() * Math.PI/6;
	hHand.x2.baseVal.value = 40 * Math.cos(angle);
	hHand.y2.baseVal.value = 40 * Math.sin(angle);
}
updateSeconds();
updateMinutes();
updateHours();
setInterval('updateSeconds()', 1000);

If you're crafty you'll pick up that the multiplications were simplied. seconds * 6 * Math.PI / 180° is the same thing as seconds * Math.PI / 30°. Now, all we do is set the baseVal of the appropriate attribute to the new value (x1 and y1 are anchored at (0, 0) remember), and the renderer updates the line for us. And of course, make sure to update the next largest timekeeper if the current value is 0. We now have a clock (which will be seen in action on the next page).

Back: An Introduction, Next: Compatibility