SVG Maker Project - Clock

This project details how to get started coding with Scalable Vector Graphics (SVG) by creating a Clock. SVG is a web standard, therefore, this code will work in any browser. Edit this file with a simple text editor like Notepad on Windows or any editor that will allow you to save just the text (without embedded formatting comands). Make sure to save the file with a .svg extension, then open your file in a browser. Make a few changes in the editor, save it, and refresh the browser to see the effect of the change.

The code for the image on the left is shown below as step 1. It uses SVG elements, circle, g, text, and path. It introduces the <style> element containing styles that are applied to the SVG elements. Since styles are CSS format and not XML format, they must be enclosed with a <![CDATA[ ... ]]> section to tell the browser not to try to interpret the text as image elements.

The #minutes and #hours define styles to be applied to elements with the id of minutes and hours respectively. The styles are enclosed in braces { ... }. The styles preceded with a '.' are applied to elements with a class attribute of the same name. Note the class attribute on the <circle> and <g> elements.

Coding the Clock - Step 1

<svg width="100%" height="100%" viewBox="0 0 300 300"
xmlns="http://www.w3.org/2000/svg">

<style type="text/css"><![CDATA[
#minutes {stroke-width:1;fill:#e5e5e5;stroke:red;opacity:0.9;}
#hours {stroke-width:1;fill:#e5e5e5;stroke:red;}
.numbers {fill:#e5e5e5;font-size:28px;font-weight:bold;text-anchor:middle;}
.ticks {fill:red;stroke:red;stroke-width:3;}
.rim {fill:red;stroke:#cccccc;stroke-width:4;}
.face {fill:#e5e5e5;stroke:#cccccc;stroke-width:4;}
]]>
</style>

<circle cx="150" cy="150" r="147" class="rim" />
<circle cx="150" cy="150" r="110" class="face" />

<g class="numbers">
<text x="215" y="51">1</text>
<text x="262" y="99">2</text>
<text x="277" y="161">3</text>
<text x="260" y="222">4</text>
<text x="215" y="271">5</text>
<text x="150" y="289">6</text>
<text x="85" y="270">7</text>
<text x="38" y="222">8</text>
<text x="23" y="161">9</text>
<text x="38" y="98">10</text>
<text x="85" y="50">11</text>
<text x="150" y="32">12</text>
</g>

<path id="hours" d="M145,170L155,170L150,85z" transform="rotate(90,150,150)" />
<path id="minutes" d="M146,180L154,180L150,52z" transform="rotate(0,150,150)" />

</svg>

The svg element viewBox attribute defines the container for the image to use (0,0) as the upper left corner and (300,300) as the lower right corner. Therefore our image will have a width and height of 300 pixels, but will scale to 100% of the width and height of the container the browser will put the image into because the width and height attributes are set to 100%.

The circle elements have attributes: cx, cy, r, and class. cx and cy define the coordinates of the center of the circle. They are both set to 150, which is exactly in the middle of the 300x300 pixel image. The r attribute defines the radius of the circle, and the class attribute defines the name of the class of styles to be applied to the element.

The <g> element allow grouping elements. The elements contained in the <g> and </g> elements are considered to be child elements and the <g> element is the parent. Styles applied to the parent are inherited by the child elements. Therefore in the image, the text elements will all have the numbers class of styles applied resulting in a consistent look.

The path elements have attributes: id, d, and transform. The id attribute will be used to apply any styles defined with that id preceded with a # sign. The d attribute takes many commands to draw almost any shape imagined. In this case, the commands used are: M - moveto(x,y), L - lineto(x,y), and z - close path (back to the start). The path elements draw the hour and minute hands on the clock and are simply triangles. The transform attribute is very powerful and can have multiple transformations applied to any element or group of elements. In this case, the path elements are rotated 90 and 0 degrees respectfully about point (150,150) which is the center of the clock and also the center of the image.

Not too hard. The clock has a good start, but it could some lines to clearly show where the hours are.

Adding Hour Markers - Step 2

<g class="ticks">
<path d="M150,43L150,50" />
<path d="M150,43L150,50" transform="rotate(30,150,150)" />
<path d="M150,43L150,50" transform="rotate(60,150,150)" />
<path d="M150,43L150,50" transform="rotate(90,150,150)" />
<path d="M150,43L150,50" transform="rotate(120,150,150)" />
<path d="M150,43L150,50" transform="rotate(150,150,150)" />
<path d="M150,43L150,50" transform="rotate(180,150,150)" />
<path d="M150,43L150,50" transform="rotate(210,150,150)" />
<path d="M150,43L150,50" transform="rotate(240,150,150)" />
<path d="M150,43L150,50" transform="rotate(270,150,150)" />
<path d="M150,43L150,50" transform="rotate(300,150,150)" />
<path d="M150,43L150,50" transform="rotate(330,150,150)" />
</g>

The image to the left adds some lines by each hour to clearly define the hour. The code above adds a group of path elements with the ticks class of styles applied to each one. Looking at the first path element, the d attribute tells the browser to move to (150,43) and draw a line to (150,50). That is a vertical line 7 pixels long at x-cordinate 150 - the center of the width of the image. It is the line below hour 12.

The next 11 path elements draw the same line, but each one is transformed by rotating it about the center of the clock (150,150). Since there are 12 hours in the 360 degree circle, each one is rotated by and additional 360/12=30 degrees. Straightforward and simple.

Adding Minute Markers - Step 3

<g class="ticks" style="stroke-width:0.5;">
<circle cx="150" cy="47" r="1" transform="rotate(6,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(12,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(18,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(24,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(36,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(42,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(48,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(54,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(66,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(72,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(78,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(84,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(96,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(102,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(108,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(114,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(126,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(132,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(138,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(144,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(156,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(162,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(168,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(174,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(186,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(192,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(198,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(204,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(216,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(222,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(228,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(234,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(246,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(252,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(258,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(264,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(276,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(282,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(288,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(294,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(306,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(312,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(318,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(324,150,150)" />

<circle cx="150" cy="47" r="1" transform="rotate(336,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(342,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(348,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(354,150,150)" />
</g>

The image to the left adds a dot for each minute between the hours. The code above adds a group of circle elements with the ticks class of styles applied to each one. But the group element also has a style attribute, overriding the stroke-width of 3 pixels defined by the ticks class with a stroke-width of 0.5 pixels. Otherwise the dots would be too fat. Looking at the first circle, it has a center of (150,47).

The circle is directly below the hour 12, before it is transformed. Since there are 60 minutes in the 360 degree clock, each minute is rotated an additional 360/60 = 6 degrees. But there are 48 of them. Copy and paste the first one 47 times, then change the rotation angle by adding 6 degrees to each one - 12 degrees to skip over the hour mark.

Straightforward, simple, but that is a lot of code. Is there a better way?

A Way with Less Lines of Code

<g id="t1" class="ticks">
<path d="M150,43L150,50" />
<g class="ticks" style="stroke-width:0.5;">
<circle cx="150" cy="47" r="1" transform="rotate(6,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(12,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(18,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(24,150,150)" />
</g>
</g>

<use xlink:href="#t1" transform="rotate(30,150,150)" />
<use xlink:href="#t1" transform="rotate(60,150,150)" />
<use xlink:href="#t1" transform="rotate(90,150,150)" />
<use xlink:href="#t1" transform="rotate(120,150,150)" />
<use xlink:href="#t1" transform="rotate(150,150,150)" />
<use xlink:href="#t1" transform="rotate(180,150,150)" />
<use xlink:href="#t1" transform="rotate(210,150,150)" />
<use xlink:href="#t1" transform="rotate(240,150,150)" />
<use xlink:href="#t1" transform="rotate(270,150,150)" />
<use xlink:href="#t1" transform="rotate(300,150,150)" />
<use xlink:href="#t1" transform="rotate(330,150,150)" />

The code above defines 2 groups for the hours and minute marks. They are the same as previously seen, but the first group is given the id of "t1". Only hour 12 and minutes 1, 2, 3, and 4 markers are defined. The t1 grouping is reused below as defined in the 11 <use> elements. The use element has an xlink:href attribute that references the id="t1" group of elements. The attribute precedes the id reference with a # sign. Each is rotated an additional 360/12 = 30 degrees using the transformation attribute to rotate the object about (150,150) which is the center of the clock.

Much nicer. Less code is good. It does require the xlink namespace be defined in the svg element as shown below. That tells the browser, this attribute is not an SVG attribute, but is defined by the XLINK specification - or something like that.

<svg width="100%" height="100%" viewBox="0 0 300 300"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">

Adding the Script - Step 4

1 2 3 4 5 6 7 8 9 10 11 12

By adding some javascript we can make the clock display the current time. Like the Day Calendar project, the code uses the javascript Date object which provides the functions needed to get the current time. The code belows shows the javascript used in the image.


<script type="application/x-javascript"><![CDATA[

window.addEventListener("load", setClock);

function setClock()
{
var element;
var min;
var deg;
var dt = new Date();

min = dt.getMinutes();
deg = 6*min;
element = document.getElementById("minutes");
element.setAttribute("transform","rotate("+deg+",150,150)");

hr = dt.getHours();
deg = 30*hr + 30*(min/60);
element = document.getElementById("hours");
element.setAttribute("transform","rotate("+deg+",150,150)");

setTimeout(setClock,10000);
}

]]>
</script>

Understanding the Javascript

The first line of javascript adds an event listener which will call the setClock() function when the image is loaded.

The setClock() function does all the work. It defines some variables needed and referenced in the code. The dt variable is set to a new copy of the javascript Date object, which provides access to the device's (computer, mobile device, etc.) current date and time.

First the code sets the variable, min, to the value returned by the Date object's getMinutes() function (0-59). Then sets variable, deg, equal to the minutes times 6. There are 6 degrees per minute (360/60). Using the DOM (Document Object Model) objects getElementById() function the code sets the element variable to the element with id="minutes". Then using that object, calls the DOM setAttribute() function to update the transform attribute with the number of degrees to rotate the object about (150,150).

Next the code sets the variable, hr, to the value returned by the Date object's getHours() function (0-23). Then sets the variable, deg, equal to 30 times the hour plus 30 times the minutes/60 to calculate the number of degrees to rotate the hour hand. Using the DOM (Document Object Model) object's getElementById() function the code sets the element variable to the element with id="hours". Then using that object, calls the DOM setAttribute() function to update the transform attribute with the number of degrees to rotate the object about (150,150).

Lastly, the code sets a timer to call the setClock() function after 10000 milliseconds (10 seconds) expire. So the clock will update the clock's time every 10 seconds - by adjusting the rotate transform on the minute and hour hands.

That's it! Below is the entire code for the SVG image of a Clock as displayed on this page. Copy and paste it into a text editor, then save it as an .svg file, then open that file in a browser.

Edit the file. Try changing the colors, text positioning, etc in the text editor, save it, refresh the browser and see the change. For a challenge, try changing the script to adjust the minute hand rotation by adding degrees to rotate based on the current seconds from the Date object, so that the minute hand will move between minutes as the time updates. Hint: look at how the javascript calculates the degrees of rotation for the hour hand.

Entire Code of the SVG Clock

<svg width="100%" height="100%" viewBox="0 0 300 300"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">

<script type="application/x-javascript"><![CDATA[

window.addEventListener("load", setClock);

function setClock()
{
var element;
var min;
var deg;
var dt = new Date();

min = dt.getMinutes();
deg = 6*min;
element = document.getElementById("minutes");
element.setAttribute("transform","rotate("+deg+",150,150)");

hr = dt.getHours();
deg = 30*hr + 30*(min/60);
element = document.getElementById("hours");
element.setAttribute("transform","rotate("+deg+",150,150)");

setTimeout(setClock,10000);
}
]]>
</script>

<style type="text/css"><![CDATA[
#minutes {stroke-width:1;fill:#e5e5e5;stroke:red;opacity:0.9;}
#hours {stroke-width:1;fill:#e5e5e5;stroke:red;}
.numbers {fill:#e5e5e5;font-size:28px;font-weight:bold;text-anchor:middle;}
.ticks {fill:red;stroke:red;stroke-width:3;}
.rim {fill:red;stroke:#cccccc;stroke-width:4;}
.face {fill:#e5e5e5;stroke:#cccccc;stroke-width:4;}
]]>
</style>

<circle cx="150" cy="150" r="147" class="rim" />
<circle cx="150" cy="150" r="110" class="face" />

<g class="numbers">
<text x="215" y="51">1</text>
<text x="262" y="99">2</text>
<text x="277" y="161">3</text>
<text x="260" y="222">4</text>
<text x="215" y="271">5</text>
<text x="150" y="289">6</text>
<text x="85" y="270">7</text>
<text x="38" y="222">8</text>
<text x="23" y="161">9</text>
<text x="38" y="98">10</text>
<text x="85" y="50">11</text>
<text x="150" y="32">12</text>
</g>

<g id="t1" class="ticks">
<path d="M150,43L150,50" />
<g class="ticks" style="stroke-width:0.5;">
<circle cx="150" cy="47" r="1" transform="rotate(6,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(12,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(18,150,150)" />
<circle cx="150" cy="47" r="1" transform="rotate(24,150,150)" />
</g>
</g>

<use xlink:href="#t1" transform="rotate(30,150,150)" />
<use xlink:href="#t1" transform="rotate(60,150,150)" />
<use xlink:href="#t1" transform="rotate(90,150,150)" />
<use xlink:href="#t1" transform="rotate(120,150,150)" />
<use xlink:href="#t1" transform="rotate(150,150,150)" />
<use xlink:href="#t1" transform="rotate(180,150,150)" />
<use xlink:href="#t1" transform="rotate(210,150,150)" />
<use xlink:href="#t1" transform="rotate(240,150,150)" />
<use xlink:href="#t1" transform="rotate(270,150,150)" />
<use xlink:href="#t1" transform="rotate(300,150,150)" />
<use xlink:href="#t1" transform="rotate(330,150,150)" />

<path id="hours" d="M145,170L155,170L150,85z" transform="rotate(90,150,150)" />
<path id="minutes" d="M146,180L154,180L150,52z" transform="rotate(0,150,150)" />

</svg>

SVG Maker Projects - coding SVG images - at STEAMcoded.org

STEAMcoded.org STEAMcoded.org