Sunday 22 May 2016

Web Tutorial: The Javascript Clockface

Tick-tock!

Today's web tutorial will revolve around creating a clockface in JavaScript. It will move like a clock, with the hands of the clock pointing at the appropriate positions.

How is this useful?

Er, well actually it's not very useful. But it's good for exercising what you know about JavaScript Timer functions, transformations and the like.

Without further ado, here's the starting HTML.
<html>
    <head>
        <title>Clock</title>

        <style>
            #clockface
            {
                width:300px;
                height:300px;
                border-radius:50%;
                border:5px solid #FFDD00;
                margin-left:auto;
                margin-right:auto;
                margin-top:10%;
            }           
        </style>

        <script>

        </script>
    </head>
    <body>
        <div id="clockface">

        </div>
    </body>
</html>


So we have a div with an id of clockface, and this is what its CSS styling does.
- width:300px, height:300px sets the height and width.
- border-radius:50% makes it circular.
- border:5px solid #FFDD00 gives it a nice thick orange border.
- margin-left:auto, margin-right:auto places the clockface in the middle of the screen.
- margin-top:10% just gives the clockface a little spacing from the top.

It should look like this now.


Now for the numbers!

What's a clockface without numbers, right? Let's add those little suckers. We'll need some JavaScript for this. But first, the HTML and styling.
<html>
    <head>
        <title>Clock</title>

        <style>
            #clockface
            {
                width:300px;
                height:300px;
                border-radius:50%;
                border:5px solid #FFDD00;
                margin-left:auto;
                margin-right:auto;
                margin-top:10%;
            }

            .clocksegment
            {
                position:absolute;
                z-index:5;
                width:20px;
                height:150px;
                margin-left:140px;
                margin-top:0px;
                -webkit-transform-origin:50% 100%;
                transform-origin:50% 100%;
                border:1px solid #FF0000;
            }

            .clocksegmentno
            {
                width:100%;
                height:21px;
                -webkit-transform-origin:50% 50%;
                transform-origin:50% 50%;
                color:#444444;
                font-size:15px;
                text-align:center;
                border:1px solid #00FF00;
            }

            .clocksegmentno_offset
            {
                background-color:#444444;
                width:1px;
                height:5px;
                margin-left:auto;
                margin-right:auto;
                margin-top:-10px;
            }           
        </style>

        <script>

        </script>
    </head>
    <body>
        <div id="clockface">
            <div id="segment1" class="clocksegment">
                <div id="segmentno1" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
        </div>
    </body>
</html>


Here we've added one div with the id of segment1 and a CSS class of clocksegment within the clockface div, then nested two other divs inside the segment1 div. The first div contains the number and has an id of segmentno1 and a CSS class clocksegmentno. The second div has no id and is styled using the CSS class clocksegmentno_offset.

Here's what the clocksegment CSS class does.
- position:absolute, z-index:5 ensures that the segments can overlap within the clockface div.
- width:20px, height:150px because each segment is half the height of the clockface div, and should occupy only that much width.
- margin-left:140px,margin-top:0px since the width of the segment is 20 pixels, half of that would be 10. And since half the width of the clockface div is 150 pixels, offsetting the left side of the segment by 150-10=140 pixels places the segment dead center top of the clockface.
- transform-origin:50% 100% because you'll want to rotate the segment by its bottom center. This is gonna be important later.
- border:1px solid #FF0000 for now, so you can see what's taking place.

Here's what the clocksegmentno CSS class does.
- width:100% sets the width to be equal to that of its parent, which would be 20 pixels.
- height:20px to allocate that amount of space for the number.
- transform-origin:50% 50% because the numbers need to be rotated later, by the center point.
- color:#444444, font-size:15px are cosmetic values, change as you will. But the font size shouldn't be too large or too small. There's a certain amount of wriggle room.
- text-align:center to ensure that no matter what width the number takes up, its placement is consistent.
- border:1px solid #00FF00 for now, so you can see what's taking place.

Here's what the clocksegmentno_offset CSS class does. This is pretty much just a thin grey line pointing to the number.
- background-color:#444444, width:1px, height:5px turns this into a very thin div with a grey background color.
- margin-left:auto, margin-right:auto keeps this in the center of its parent div.
- margin-top:-10px reduces the spacing between the number and the pointer.

This is what you should have...


Now add eleven more of these segments, and number them accordingly. Remember to change the numbering of the nested divs as well!
        <div id="clockface">
            <div id="segment1" class="clocksegment">
                <div id="segmentno1" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment2" class="clocksegment">
                <div id="segmentno2" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment3" class="clocksegment">
                <div id="segmentno3" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment4" class="clocksegment">
                <div id="segmentno4" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment5" class="clocksegment">
                <div id="segmentno5" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment6" class="clocksegment">
                <div id="segmentno6" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment7" class="clocksegment">
                <div id="segmentno7" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment8" class="clocksegment">
                <div id="segmentno8" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment9" class="clocksegment">
                <div id="segmentno9" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment10" class="clocksegment">
                <div id="segmentno10" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment11" class="clocksegment">
                <div id="segmentno11" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment12" class="clocksegment">
                <div id="segmentno12" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
        </div>


Now see what you did. They're all overlapped, aren't they? So you will see no apparent difference in the output. We're going to rectify that with some JavaScript! Add the startclock() function to your script tag. Here, we run a For loop which iterates through all twelve segments.
        <script>
            function startclock()
            {
                for (i=1;i<=12;i++)
                {

                }
            }
        </script>


And modify your HTML. This ensures that startclock() is fired off right at the start.
    <body onload="startclock();">


Now let's get back to the startclock() function. First of all, rotate each segment by an increasing 360/12=30 degrees. segment1 will be rotated by 30 degrees, segment2 by 60 degrees, etc. Unless it's segment12 - segment12 stays put.
function startclock()
{
    for (i=1;i<=12;i++)
    {
        if (i<12)
        {
            document.getElementById("segment"+i).style.transform="rotate("+(i*30)+"deg)";
            document.getElementById("segment"+i).style.webkitTransform="rotate("+(i*30)+"deg)";

        }
    }
}


Presto!


Now to fill in the numbers. This line of code is outside of the If-else block because segment12 also needs to have its number displayed.
    for (i=1;i<=12;i++)
    {
        if (i<12)
        {
            document.getElementById("segment"+i).style.transform="rotate("+(i*30)+"deg)";
            document.getElementById("segment"+i).style.webkitTransform="rotate("+(i*30)+"deg)";          
        }

        document.getElementById("segmentno"+i).innerHTML=i;
    }


Here we are, the numbers are in! But do you see a problem? That's right, the numbers have been rotated along with their parent divs. This is bad because "6" now looks like "9".


We're going to rotate all the number divs independently of their parent divs. This line of code rotates the ppropriate div in the opposite direction of which their parent div was rotated.
    for (i=1;i<=12;i++)
    {
        if (i<12)
        {
            document.getElementById("segment"+i).style.transform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.transform="rotate(-"+(i*30)+"deg)";
            document.getElementById("segment"+i).style.webkitTransform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.webkitTransform="rotate(-"+(i*30)+"deg)";              
        }

        document.getElementById("segmentno"+i).innerHTML=i;
    }


Like magic!


Now set all the border properties of the clocksegment and clocksegmentno CSS classes to 0px.
            .clocksegment
            {
                position:absolute;
                z-index:5;
                width:20px;
                height:150px;
                margin-left:140px;
                margin-top:0px;
                -webkit-transform-origin:50% 100%;
                transform-origin:50% 100%;
                border:0px solid #FF0000;
            }

            .clocksegmentno
            {
                width:100%;
                height:21px;
                -webkit-transform-origin:50% 50%; 
                transform-origin:50% 50%;
                color:#444444;
                font-size:15px;
                text-align:center;
                border:0px solid #00FF00;
            }
           

There you go.


Time to add the hands!

Specifically, the hour hand, minute hand and second hand.
        <div id="clockface">
            <div id="segment1" class="clocksegment">
                <div id="segmentno1" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment2" class="clocksegment">
                <div id="segmentno2" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment3" class="clocksegment">
                <div id="segmentno3" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment4" class="clocksegment">
                <div id="segmentno4" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment5" class="clocksegment">
                <div id="segmentno5" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment6" class="clocksegment">
                <div id="segmentno6" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment7" class="clocksegment">
                <div id="segmentno7" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment8" class="clocksegment">
                <div id="segmentno8" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment9" class="clocksegment">
                <div id="segmentno9" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment10" class="clocksegment">
                <div id="segmentno10" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment11" class="clocksegment">
                <div id="segmentno11" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>
            <div id="segment12" class="clocksegment">
                <div id="segmentno12" class="clocksegmentno"></div>
                <br />
                <div class="clocksegmentno_offset"></div>
            </div>

            <div id="secondhand" class="hand"></div>
            <div id="minutehand" class="hand"></div>
            <div id="hourhand" class="hand"></div>
        </div>


Here's the CSS styling.
        <style>
            #clockface
            {
                width:300px;
                height:300px;
                border-radius:50%;
                border:5px solid #FFDD00;
                margin-left:auto;
                margin-right:auto;
                margin-top:10%;
            }

            #clockcenter
            {
                position:absolute;
                z-index:20;
                width:11px;
                height:11px;
                border-radius:50%;
                background-color: #444444;
                margin-left:145px;
                margin-top:145px;
            }

            .clocksegment
            {
                position:absolute;
                z-index:5;
                width:20px;
                height:150px;
                margin-left:140px;
                margin-top:0px;
                -webkit-transform-origin:50% 100%; 
                transform-origin:50% 100%;
            }

            .clocksegmentno
            {
                width:100%;
                height:20px;
                -webkit-transform-origin:50% 50%; 
                transform-origin:50% 50%;
                color:#444444;
                font-size:15px;
                text-align:center;
            }

            .clocksegmentno_offset
            {
                background-color:#444444;
                width:1px;
                height:5px;
                margin-left:auto;
                margin-right:auto;
                margin-top:-10px;
            }

            .hand
            {
                position:absolute;
                z-index:10;
                -webkit-transform-origin:50% 100%;
                -webkit-transform:rotate(0deg);
                transform-origin:50% 100%;
                transform:rotate(0deg);
            }

            #secondhand
            {
                margin-left:149px;
                margin-top:50px;
                width:3px;
                height:100px;
                background-color:#444444;
            }

            #minutehand
            {
                margin-left:148px;
                margin-top:20px;
                width:5px;
                height:130px;
                background-color:#222222;
            }

            #hourhand
            {
                margin-left:147px;
                margin-top:70px;
                width:7px;
                height:80px;
                background-color:#000000;
            }           
        </style>


The CSS class hand is a style that encompasses all the properties that the hour hand, minute hand and second hand have in common.
- position:absolute,z-index:10 because you need these guys to overlap, and stay above the segments.
- transform-origin:50% 100% because each of these "hands" is actually a rectangular div which will be rotated by its bottom center,
- transform:rotate(0deg) is the default rotation for all of them. This needs to be specified because it will change later.

The hourhand, minutehand and secondhand divs all have different heights, widths and background colors. And because they have different heights and widths, they have different margin-top and margin-left properties too. The end result is that their center bottom has to be pointing dead center of the clockface div. The math is pretty simple, figure it out yourself.

This is what you should have right now. Of course, it's not displaying the time correctly, unless it happens to be 12 noon or 12 midnight for you. We'll move on to that next.


Displaying the correct time at the start

Modify your startclock() function like so. This declares the nowdate variable, which is set to the current date and time. Also the variables secondpos, minutepos and hourpos, which will hold the values for degrees of the rotation of your hands.

For more on JavaScript Date functions, check the following link out: (http://www.w3schools.com/js/js_dates.asp)

function startclock()
{
    var nowdate=new Date();
    var secondpos,minutepos,hourpos;


    for (i=1;i<=12;i++)
    {
        if (i<12)
        {
            document.getElementById("segment"+i).style.transform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.transform="rotate(-"+(i*30)+"deg)";
            document.getElementById("segment"+i).style.webkitTransform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.webkitTransform="rotate(-"+(i*30)+"deg)";              
        }

        document.getElementById("segmentno"+i).innerHTML=i;
    }
}


Now this. Every second, the second hand moves by 6 degrees. How to we figure that? Well, the second hand moves 60 times in one revolution. One revolution is 360 degrees. So 360/60 is 6! So here we get the current value of nowdate's seconds multiplied by 6 and assign the value to the variable secondpos. If the result is 360, we don't need to move the second hand at all. Otherwise, we move the second hand by secondpos degrees.
function startclock()
{
    var nowdate=new Date();
    var secondpos,minutepos,hourpos;

    for (i=1;i<=12;i++)
    {
        if (i<12)
        {
            document.getElementById("segment"+i).style.transform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.transform="rotate(-"+(i*30)+"deg)";
            document.getElementById("segment"+i).style.webkitTransform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.webkitTransform="rotate(-"+(i*30)+"deg)";              
        }

        document.getElementById("segmentno"+i).innerHTML=i;
    }

    secondpos=nowdate.getSeconds()*6;
    secondpos=(secondpos==360?0:secondpos);

    document.getElementById("secondhand").style.transform="rotate("+secondpos+"deg)";

    document.getElementById("secondhand").style.webkitTransform="rotate("+secondpos+"deg)";

}


The logic is repeated for minutes.
function startclock()
{
    var nowdate=new Date();
    var secondpos,minutepos,hourpos;

    for (i=1;i<=12;i++)
    {
        if (i<12)
        {
            document.getElementById("segment"+i).style.transform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.transform="rotate(-"+(i*30)+"deg)";
            document.getElementById("segment"+i).style.webkitTransform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.webkitTransform="rotate(-"+(i*30)+"deg)";              
        }

        document.getElementById("segmentno"+i).innerHTML=i;
    }

    secondpos=nowdate.getSeconds()*6;
    secondpos=(secondpos==360?0:secondpos);

    minutepos=nowdate.getMinutes()*6;
    minutepos=(minutepos==360?0:minutepos);


    document.getElementById("secondhand").style.transform="rotate("+secondpos+"deg)";
    document.getElementById("minutehand").style.transform="rotate("+minutepos+"deg)";

    document.getElementById("secondhand").style.webkitTransform="rotate("+secondpos+"deg)";
    document.getElementById("minutehand").style.webkitTransform="rotate("+minutepos+"deg)";
}


Next, the hour hand. This one is slightly different. The time operates on a 24 hour format, so you need to figure out which number the hour hand points towards. For instance, whether the hour 4 or 16 (4am or 4pm), the hour hand should still point towards "4".

So first, we get the number of hours in the nowdate variable. If the value's greater than 12, subtract 12. Then multiply by 30. Why 30? Because there are 12 numbers, each one separated by 30 degrees! If the result is 360, change it to 0, because that would mean the hour hand doesn't budge.

But that's not all. The hour hand doesn't point right at the number unless the minute hand is pointing at 12, ie on the hour. So we figure out how much more the hour hand needs to move. Each number is separated by 30 degrees. So we take the minute hand's position and divide by 360 to obtain the ratio. The ratio is then multiplied by 30, and the result added to hourpos.
function startclock()
{
    var nowdate=new Date();
    var secondpos,minutepos,hourpos;

    for (i=1;i<=12;i++)
    {
        if (i<12)
        {
            document.getElementById("segment"+i).style.transform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.transform="rotate(-"+(i*30)+"deg)";
            document.getElementById("segment"+i).style.webkitTransform="rotate("+(i*30)+"deg)";
            document.getElementById("segmentno"+i).style.webkitTransform="rotate(-"+(i*30)+"deg)";              
        }

        document.getElementById("segmentno"+i).innerHTML=i;
    }

    secondpos=nowdate.getSeconds()*6;
    secondpos=(secondpos==360?0:secondpos);

    minutepos=nowdate.getMinutes()*6;
    minutepos=(minutepos==360?0:minutepos);

    hourpos=nowdate.getHours();
    hourpos=(hourpos>12?hourpos-12:hourpos)*30;
    hourpos=(hourpos==360?0:hourpos);
    hourpos=hourpos+((minutepos/360)*30);


    document.getElementById("secondhand").style.transform="rotate("+secondpos+"deg)";
    document.getElementById("minutehand").style.transform="rotate("+minutepos+"deg)";
    document.getElementById("hourhand").style.transform="rotate("+hourpos+"deg)";

    document.getElementById("secondhand").style.webkitTransform="rotate("+secondpos+"deg)";
    document.getElementById("minutehand").style.webkitTransform="rotate("+minutepos+"deg)";
    document.getElementById("hourhand").style.webkitTransform="rotate("+hourpos+"deg)";
}


There you go, now it tells the time! But since it's not moving, it only tells the correct time twice a day. Heh heh.


Making the hands move

Now this is simple. We could simply make this program calculate the time and display the positions of the hands every second. But that's lazy and inefficient. The minute and hour hands only move once a minute. So let's add the move_secondhand() function, and set it to an interval of 1 second.

Check out this link to learn more about JavaScript Timer functions! (http://www.w3schools.com/js/js_timing.asp)
    document.getElementById("secondhand").style.webkitTransform="rotate("+secondpos+"deg)";
    document.getElementById("minutehand").style.webkitTransform="rotate("+minutepos+"deg)";
    document.getElementById("hourhand").style.webkitTransform="rotate("+hourpos+"deg)";
  
    setInterval(function(){move_secondhand();},1000);
}

function move_secondhand()
{

}


First, we get the current position of the second hand. For this, because we'll be doing this repeatedly for different hands, we'll make the getcurrentpos() function. This function accepts the name of the object to be moved, and returns a number that says how many degrees this element is currently rotated by. For example, if the current value of the secondhand div's transform property is "rotate(20deg)", we strip out "rotate(" and "deg)" to give you the number.

Check out JavaScript's String replace() method. (http://www.w3schools.com/jsref/jsref_replace.asp)
    document.getElementById("secondhand").style.webkitTransform="rotate("+secondpos+"deg)";
    document.getElementById("minutehand").style.webkitTransform="rotate("+minutepos+"deg)";
    document.getElementById("hourhand").style.webkitTransform="rotate("+hourpos+"deg)";
  
    setInterval(function(){move_secondhand();},1000);
}

function getcurrentpos(varobj)
{
    var currentpos=document.getElementById(varobj).style.transform+document.getElementById(varobj).style.webkitTransform;
    currentpos=currentpos.replace("rotate(","");
    currentpos=currentpos.replace("deg)","");
    currentpos=parseInt(currentpos);

    return currentpos;
}


function move_secondhand()
{
    var pos=getcurrentpos("secondhand");
}


Then we increment that value by 6. The second hand moves by 6 degrees every time, remember? And then we rotate the secondhand div.
function move_secondhand()
{
    var pos=getcurrentpos("secondhand");
    pos+=6;

    document.getElementById("secondhand").style.transform="rotate("+pos+"deg)";
    document.getElementById("secondhand").style.webkitTransform="rotate("+pos+"deg)";

}


But wait, there's more. If the second hand has moved to 360 degrees, we reset the rotation to 0, and then call the move_minutehand() function. Because once the second hand moves to 12, the minute hand moves!
function move_secondhand()
{
    var pos=getcurrentpos("secondhand");
    pos+=6;

    if (pos==360)
    {
        pos=0;
        move_minutehand();
    }


    document.getElementById("secondhand").style.transform="rotate("+pos+"deg)";
    document.getElementById("secondhand").style.webkitTransform="rotate("+pos+"deg)";
}

function move_minutehand()
{

 
}


So the first part of the move_minutehand() function is almost identical to the move_secondhand() function...
function move_minutehand()
{
    var pos=getcurrentpos("minutehand");
    pos+=6;

    if (pos==360)
    {
        pos=0;
    }

    document.getElementById("minutehand").style.transform="rotate("+pos+"deg)";
    document.getElementById("minutehand").style.webkitTransform="rotate("+pos+"deg)"; 
 
}


...albeit with a slight difference. The hour hand only moves when the minute hand has travelled in multiples of 72 degrees. That means it only moves when the minute hand has moved 72, 144, 216, 288 or 360 degrees. Take note, that's 5 instances. Why's that? Because in the space of one hour, the hour hand only moves 30 degrees. And since each movement is 6 degrees, that means it only moves 30/6=5 times. 360 degrees divided by 5 (5 instances, remember?) is 72 degrees. So for every 72 degrees the minute hand travels, the hour hand moves.
function move_minutehand()
{
    var pos=getcurrentpos("minutehand");
    pos+=6;

    if (pos%72==0)
    {
        move_hourhand();
    }


    if (pos==360)
    {
        pos=0;
    }

    document.getElementById("minutehand").style.transform="rotate("+pos+"deg)";
    document.getElementById("minutehand").style.webkitTransform="rotate("+pos+"deg)";  
}

function move_hourhand()
{

}


And of course, the move_hourhand() function performs pretty much like the others, except that it doesn't cause any other parts of the clock to move.
function move_hourhand()
{
    var pos=getcurrentpos("hourhand");
    pos+=6;

    if (pos==360)
    {
        pos=0;
    }

    document.getElementById("hourhand").style.transform="rotate("+pos+"deg)";
    document.getElementById("hourhand").style.webkitTransform="rotate("+pos+"deg)";

}


There you go, check this demo out!
















Now, wasn't that a handy exercise?
T___T

No comments:

Post a Comment