<h1class="episodetitle">Project: Create an Analog Clock</h1>
<h1class="sectiontitle">Create an Analog Clock: Project Breakdown</h1>
<p>The starting point for this project will be an HTML file with an SVG image of an analog clock with hands for hours, minutes and seconds. We will use</p>
<ul>
<li>objects to get the current time</li>
<li>variables to hold relevant information</li>
<li>methods to break the time down in to constituent parts - hours, minutes and seconds</li>
<li>operators to translate that information into degrees in a circle</li>
<li>DOM element properties to correctly position the arms</li>
</ul>
<h1class="sectiontitle">Use CSS to Move Clock Hands</h1>
<p>The HTML file for the clock is shown in figure 69.</p>
<pclass="caption">Figure 69 - the HTML file for the analog clock project</p>
<p>The main parts of the HTML file are</p>
<ul>
<li>
a container called clockbox
</li>
<ul>
<li>an inline svg containing four graphic elements</li>
<ul>
<li>face which draws the clock face including marks for the hours</li>
<li>hour-arm</li>
<li>minute-arm</li>
<li>second-arm</li>
</ul>
</ul>
</ul>
<p>The face element has two circles, circle, which represents the outer edge of the clock and mid-circle, which represents the centre. For each of the arm elements, there is the arm itself and a sizing box so that we can control the size of each arm independently of the others.</p>
<p>The CSS file is quite sparse and is shown in figure 70.</p>
<pre><code>
1. /* Layout */
2. .main {
3. display: flex;
4. padding: 2em;
5. height: 90vh;
6. justify-content: center;
7. align-items: middle;
8. }
9.
10. .clockbox,
11. #clock {
12. width: 100%;
13. }
14.
15. /* Clock styles */
16. .circle {
17. fill: none;
18. stroke: #000;
19. stroke-width: 9;
20. stroke-miterlimit: 10;
21. }
22.
23. .mid-circle {
24. fill: #000;
25. }
26. .hour-marks {
27. fill: none;
28. stroke: #000;
29. stroke-width: 9;
30. stroke-miterlimit: 10;
31. }
32.
33. .hour-arm {
34. fill: none;
35. stroke: #000;
36. stroke-width: 17;
37. stroke-miterlimit: 10;
38. }
39.
40. .minute-arm {
41. fill: none;
42. stroke: #000;
43. stroke-width: 11;
44. stroke-miterlimit: 10;
45. }
46.
47. .second-arm {
48. fill: none;
49. stroke: #000;
50. stroke-width: 4;
51. stroke-miterlimit: 10;
52. }
53.
54. /* Transparent box ensuring arms center properly. */
55. .sizing-box {
56. fill: none;
57. }
58.
59. /* Make all arms rotate around the same center point. */
60. /* Optional: Use transition for animation. */
61. #hour,
62. #minute,
63. #second {
64. transform-origin: 300px 300px;
65. transition: transform .5s ease-in-out;
66. }
</code></pre>
<pclass="caption">Figure 70 - the CSS file for the analog clock project</p>
<p>The clock is centred on the page because of the line</p>
<pre><code>
display: flex;
</code></pre>
<p>This applies to the main class and the main class encompasses all the HTML to the body, so this is, in effect, global CSS and it has the effect of centring the clock in our page. More info on the display attribute can be found <ahref=" https://www.w3schools.com/cssref/pr_class_display.asp">here</a>.</p>
<p>Another important attribute is width which is applied to both the clockbox class and the clock id. Again, it is effectively global since all of the visible elements on the page are inside the div with class clockbox. The clock id targets only the clock face, not the hands.</p>
<pre><code>
.circle {
fill: none;
stroke: #000;
stroke-width: 9;
stroke-miterlimit: 10;
}
</code></pre>
<pclass="caption">Figure 71 - CSS targeting the circle class</p>
<p>The CSS for the circle class is in figure 71. This is the clock face and the stroke properties relate to the edge (or border) of the clock. Going back to the HTML, recall that the clock face is one of the graphical elements. The HTML code for this element is shown in figure 72.</p>
<pclass="caption">Figure 72 - the HTML for the element with class circle</p>
<p>Interesting things to note here are that we have a graphical element with a <g> tag and inside this, we have a circle element. So, this is the element that the CSS in figure 71 is targeting. As you might expect, cy and cx are the co-ordinates of the centre of the circle and r is the radius.</p>
<p>Going back to the CSS, the fill property is set to none since this circle represents the border of the clock face. The stroke property with a value of #000 is the colour of the circle (in this case, black). The stroke-width is the width of the visible circle so if we increase this, we will see a more prominent border.</p>
<p>The stroke-mitrelimit property puts a limit on the ratio of the length to width, in this case of the clock face border and if this limit is exceeded, we may start to see a bevelling effect as the join is converted from a miter to a bevel. To see this effect, let's look at the original version of the clock in figure 73.</p>
<pclass="caption">Figure 73 - the clock in its original version</p>
<p>The clock shown in figure 73 is what we see when the stroke-mitrelimit is set to 10. The 'length' in this example is the circumference of the circle which is a little under 800 and the width is 9 so this gives us a ratio of around 89 so this is below the imposed limit.</p>
<p>As a matter of interest, there is no visible effect if the stroke-mitrelimit property is set to 1 but I don't really understand the property or exactly what it does and these are properties that have not been covered in the course so far. For more information, the documentation can be found <ahref="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-miterlimit">here</a>. More information on the circle element can be found <ahref="https://www.w3schools.com/graphics/svg_circle.asp">here</a> and on the stroke property <ahref="https://css-tricks.com/almanac/properties/s/stroke/">here</a>.</p>
<p>The HTML in figure 72 also defines a second circle with class mid-circle. This only has one CSS rule and that is</p>
<pre><code>
fill: #000;
</code></pre>
<p>Unlike the border, this circle is not intended to be a border only so the fill value is #000 (black) and this gives us the filled in circle in the centre of the clock.</p>
<p>The CSS for the hour-marks and the hands is pretty similar with the only difference being the width. The CSS for the hour hand is shown in figure 74.</p>
<pre><code>
.hour-arm {
fill: none;
stroke: #000;
stroke-width: 17;
stroke-miterlimit: 1;
}
</code></pre>
<pclass =>Figure74-theCSSforthehourhand</p>
<p>The minute hand has a width of 11, the second hand has a width of 4 and the hour-marks have a width of 9.</p>
<p>There is also a CSS rule applied to the sizing-box class and this sets the value of fill to none. This is used to centre the clock's arms.</p>
<p>Finally, we have a group with two rules that apply to elements with an id of hour, minute or second as shown in figure 75.</p>
<pre><code>
#hour,
#minute,
#second {
transform-origin: 300px 300px;
transition: transform .5s ease-in-out;
}
</code></pre>
<pclass="caption">Figure 75 - the CSS for the group of ids which targets elements with an id of hour, minute or second"</p>
<p>These help to control the movement of the hands.</p>
<pclass="caption">Figure 76 - the analog clock shown in Chrome with the Element inspector open</p>
<p>In figure 76, we can see the clock again. This is being viewed in Chrome and we have the elements in the lower left with the CSS styles in the lower right. Specifically, we can see the same CSS rules as shown in figure 75. The movement of the hands is going to be handled with a CSS rule and to see how this will work, we will add a CSS rule by clicking the + sign in the Styles panel. As an example, let's say we want to rotate the minute hand by 15 degrees, we will do that by adding this rule.</p>
<pre><code>
g#minute {
transform: rotate(15deg);
}
</code></pre>
<p>For our clock, the method we will use to rotate the arms is similar to this, but we will be using JavaScript to apply the transform property via an inline CSS style.</p>
<h1class="sectiontitle">Use JavaScript to Move Clock Hands</h1>
<p>To start with, the script.js file has three constants as shown in figure 77.</p>
<pclass="caption">Figure 77 - the script.js file for our analog clock project</p>
<p>These target the individual hands on our clock so that we can easily reference them in our JavaScript code.</p>
<p>Next, we will add three variables declared with let (which makes them local to the code block in which they are declared - see the documentation for let <ahref="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let">here</a>). Eventually, these variables will hold the number of degrees by which each hand should be rotated.</p>
<pre><code>
let hrPosition = 20;
let minPosition = 130;
let secPosition = 267;
</code></pre>
<pclass="caption">Figure 78 - our three variables holding the number of degrees by which each hand should be rotated</p>
<p>The three variables are shown in figure 78 and they have been given initial values. Later, we will do some calculations to ensure that these values are as accurate as possible.</p>
<p>Next, we will use these variables to apply an inline CSS transform rule much like the one we applied at the end of the last section to actually rotate the hands. For example, the rule applied to the second hand will look like this.</p>
<p>We can also add similar rules for the minute and second hands. It is important to remember that at this point, we are simply rotating each of the hands by a set number of degrees. This changes the position of each hand individually so in effect, we are setting the clock to a specific time as shown in figure 79.</p>
<p>To allow us to set the correct time using JavaScript, we will set up a variable that holds the current time.</p>
<pre><code>
var date = new Date();
</code></pre>
<p>We can also log this to the console to see what output this is giving us which is, in this case</p>
<pre><code>
Fri Mar 19 2021 11:02:33 GMT+0000 (Greenwich Mean Time)
</code></pre>
<p>We can use methods for the date object (see the documentation here). For example, if we want to get the hours, we can create a variable (which we will call hr) and use the getHours method to set its value to the hours part of the current time.</p>
<pre><code>
let hr = date.getHours();
</code></pre>
<p>Similarly, we can create variables called sec and min and use the getSeconds and getMinutes methods to initialise them.</p>
<h1class="sectiontitle">Show the Current Time Using Fancy Math</h1>
<p>Now that we have individual values for the hours, minutes and seconds corresponding to the current time, we can use these values to set the time on our clock to the correct time. However, we will also need to convert these values to degrees.</p>
<p>For instance, if we look at the time which had been returned as the current time previously, this was</p>
<pre><code>
Fri Mar 19 2021 11:02:33 GMT+0000 (Greenwich Mean Time)
</code></pre>
<p>So to set the current time, we need to rotate the second hand by a number of degrees that equates to 60 seconds. The easiest way to do this is to work out how many degrees equate to a single second and multiply it by the number of seconds (which we have stored in the variable, sec).</p>
<p>There are 360 degrees in a circle and 60 seconds in a minute. So 1 seconds equates to 360/60 degrees which is 6. Similarly, a single minute equates to 6 degrees. This is based on the fact that the second hand does a full rotation every minute and there are 60 seconds in a minute. The minute hand does a full rotation every hour and there are 60 minutes in an hour.</p>
<p>This is a 12 hour clock, so the hour hand does a full rotation every 12 hours and the number of degrees equivalent to one hour is therefore 360/12 which is 30.</p>
<p>To set the time, we will move the variables shown in figure 78 so that they are after the variables we used to get the hours, minutes and seconds of the current time. Instead of initialising them with random values, we will use the hr, min and sec variables to set them to the correct number of degrees corresponding to the current time. This is not the time shown previously (11:02:33) but is the current time whenever we happen to run the code.</p>
<p>Figure 80 shows these variables again and the values we give them based on the current time.</p>
<pre><code>
let hrPosition = hr * (360/12);
let minPosition = min * (360/60);
let secPosition = sec * (360/60);
</code></pre>
<pclass="caption">Figure 80 - the same variables as shown in figure 78 but initialised based on the current time rather than random values</p>
<pclass="caption">Figure 81 - the analog clock showing the current time rather than a time based on random values</p>
<p>If we now refresh the browser, we can see that it now shows a current time of just after 12:30 and we can also see the system time (12:31) in the bottom right so this shows that the time is correct.</p>
<p>This works fine and we will see that the clock displays the correct time whenever the browser is refreshed. We are not actually moving the hands automatically yet, but we want to think about some things that will help with this when we add the appropriate code to do that later.</p>
<p>For example, the second hand rotates around the clock once a minute but we want it to rotate along with the second hand.</p>
<p>We do this by setting the value of minPosition to the following</p>
<p>What this does is to rotate the minute hand a little bit more. So this is giving us a smoother motion. If we didn't do this, whenever we refresh the page, the minute hand would move if an additional minute has passed. For example, if the time was 12:31, the minute hand would not move until 12:32.</p>
<p>Now, if we consider the fact that the number of degrees between 12:31 and 12:32 is 6 (that is one minute and so 360/60) if we divide that minute by 60 (hence the 360 / 60 /60) this is the number of degrees that the minute hand rotates every second.</p>
<p>Similarly, if we set the position of the hour hand to</p>
<pre><code>
let hrPosition = (hr * (360 / 12) + min * (360 / 60) / 12);
</code></pre>
<p>So again, we are effectively taking our clock face and splitting it into 12 hours and splitting each hour into 60 minutes so the hour hand moves along with the minute hand. That is every minute the hour hand rotates by a 60th of an hour (that is a minute) and the motion is much smoother. Since the minute hand itself is also moving based on the rotation of the second hand, the hour hand will also move based on the second hand rotation so it will change its position once a second.</p>
<h1class="sectiontitle">Make the Clock Move Forward Second by Second</h1>
<p>The script we have created to allow the clock to show the current time is static. That is, it shows the time when the browser is refreshed or the page is reloaded but then doesn't change until we refresh the page again. We need to run the code once a second in order to have it update itself in real time.</p>
<p>The first step in doing that is to take all the code that updates the time and put it into a function which we will call runTheClock.</p>
<p>We then want to call the function once per second and to do that we will use the setInterval method (the documentation for which is found <ahref="https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setInterval">here</a>).</p>
<p>The syntax is quite simple. We create a variable which we will call interval and set its value to the result of running the setInterval function. The function takes two arguments, the first of which is the name of the function we want to run and the second is the interval in milliseconds.</p>
<p>There are several things to note when running the clock like this. Firstly, the animation of the second hand is very smooth and this is because of the line</p>
<pre><code>
transition: transform .5s ease-in-out;
</code></pre>
<p>in the CSS file. We don't really see this with the minute hand and even less so with the hour hand and this is because of the fact that the animation is very smooth. Combined with the fact that the minute hand takes an hour to do a full rotation and the hour hand takes 12 hours, the motion is almost indiscernible.</p>
<p>The second thing to notice is that the second hand does something that you wouldn't see on a conventional clock. It counts round to 59 seconds in the usual way, but when it gets back to 0 it suddenly rotates backwards to 0. So when the number of seconds is increasing, the motion is clockwise but when it is decreasing, when the seconds component of the time moves from 59 seconds to 0 seconds, it rotates in an anti-clockwise direction.</p>
<p>This is caused by the same ease-in-out transformation in the CSS style sheet so if we comment this out, the second hand just keeps rotating in a clockwise direction. However, rather than the smooth animation, the second hand disappears and then reappears in its new position every time it moves.</p>
<p>However, it is actually quite easy to fix this and we will do that next.</p>
<h1class="sectiontitle">Solve the Pesky "Return to Zero" Problem</h1>
<p>Note that this "return to zero" problem also affects the minute and hour hands but its easier to see in the second hand because of the fact that it only takes one minute to do a complete rotation.</p>
<p>To resolve this, we are going to take out all of our variable declaration which we currently have in the function and we will put these before the function definition. This means that setting the time when the page is loaded or reloaded all happens just once and this is outside the function.</p>
<p>Inside the function, we will only have the code that the number of degree by which each hand must be turned along with the code to redraw the hands. We will streamline the calculations a little bit so that it is a little neater. Now, we are calculating the number of degrees that each hand rotates by every second, so we are no longer using the adjustments we made with the code earlier and we are not tying the motion of the minute hand to the second hand or the hour hand to the minute hand.</p>
<p>Again, the second hand is the simplest. It rotates 360 degrees in 60 seconds, so that is simply 360 / 60 which gives us 6.</p>
<p>The minute hand rotates 360 degrees every 60 minutes or 360 degrees every 3600 seconds so that gives us 360 / 3600 which we can simplify to 1 / 10.</p>
<p>The hour hand rotates 360 degrees every 12 hours so that is 360 / 43200 which we can simplify to 1 / 120.</p>
<p>As a quick aside, I resisted the temptation to simplify the calculations in the original version of the script. An example of this is</p>
<pre><code>
let secPosition = sec * (360 / 60);
</code></pre>
<p>which we could have written as </p>
<pre><code>
let secPosition = sec * 6;
</code></pre>
<p>However, expressing these values as multiples of 60 helps to emphasise the fact that they relate to time so inside the function, we will update the values as follows</p>
<pre><code>
hrPosition = hrPosition + (3 / 360);
minPosition = minPosition + (6 / 60);
secPosition = secPosition + (360 / 60);
</code></pre>
<p>This seems to work pretty well, we have the same smooth animation and we no longer see the "return to zero" problem. However, there is a drawback to this approach. We are no longer using the date function to get the current time. We are just doing this once, when the script runs for the first time, and then relying on the browser to update the time once every second. If the browser throttles the JavaScript which may mean that it runs slowly or misses updates so you could end up with the clock being inaccurate.</p>
<p>To some degree, we can see this if we take focus away from the page displaying the clock either by switching to a different window or a new tab in the same browser. When we go back to the clock, we see that it hasn't moved the hands so we see it quickly adjust them to the current time.</p>
<p>So, we have a number of different ways of handling the animation. We have done this by using the ease-in-out transformation and relying on the browser to keep the time accurate. This gives us the smoothest possible animation without the "return to zero" problem, but also gives us the problem of possible inaccuracy with the clock.</p>
<p>We could also have just had the function check the time every time it runs which is the original version and keep the ease-out-in transformation so we get the smooth animation without having the possible inaccuracy with the clock, but we also get the "return to zero" problem.</p>
<p>The other option would be to have the function check the time every time it runs and remove the ease-in-out transformation. This gives us the accuracy and solves the "return to zero" problem, but we lose the smooth animation.</p>
<p>So, essentially, there is a trade-off here and the approach we take may ultimately be determined by which of the possible problems we think can be ignored or which of the possible benefits of the different approaches are essential.</p>