Animate Your CSS Clock with Javascript

PR0 Challenge 7.5 [60pts]

Adapted from CSS Animation at cssanimation.rocks


Now that you’ve learned how to automatically set your clock with Javascript, the next small step is to animate it so that the clock ticks! In this challenge we’ll use a little more CSS and some Javascript to move your clock hands.


STEP 1: Find Your Clock Thimble

Start by finding your Thimble Project from the previous challenge. If you did not complete the "Assemble a CSS Clock" challenge, make sure to do that firs. To find your project:

Visit thimble.mozilla.org
Log in to Webmaker (top-right corner)
Click on your username (top-right) to reveal a drop-down menu, select:     Your Projects  
Find the project you named "Your Name's CSS Clock" from the list;

Your clock should look like this:



You should also have these basic files in your Thimble project:

  1. index.html
  2. style.css
  3. ios_clock.svg

We'll be spending most of our time in this challenge hacking style.css.

STEP 2: Modify the setSec Function

Begin by viewing your set-sec.js (JS) file in Thimble. Based on the previous challenge, the JS code in that file should look something like this:

/*
 * Starts any clocks using the user's local time
 * From: cssanimation.rocks/clocks
 * Modified by: http://hackshop.org
 */
function setSec() {
  // Get the local time using JS
  var date = new Date;
  var seconds = date.getSeconds();
  var minutes = date.getMinutes();
  var hours = date.getHours();

  // Create variable with hour hand angle in degrees
  var angle = 6 * seconds;

  // set the angle of hour hand element
  var elements = document.querySelectorAll('.seconds');
  for (var k = 0; k < elements.length; k++) {
    elements[k].style.webkitTransform = 'rotateZ('+ angle +'deg)';
    elements[k].style.transform = 'rotateZ('+ angle +'deg)';
  }
}

In this code, you set the current seconds by calling the Date function, extracting the seconds, calculating the angle, and use the rotateZ() function to set the .seconds element.

So then, how might you use the rotateZ() function to move the seconds hand every second? One answer is to use another special JS function called setInterval( cb, ms ). This function allows you to repeat a command cb every ms milliseconds.

The last step is to identify which commands need to be repeated to move the seconds hand. Can you identify which lines of code adjust the seconds hand? Let's look at the solution step by step:

  1. Calculate the current time;
  2. Convert the time to an angle;
  3. Rotate the .seconds element to that angle;

We could repeat all three of these steps, but we really only need to do #2 and 3. Why?

To rotate the second hand, add the following lines of code to your set-sec.js file, inside of the setSec function (i.e. inside of the {...}):

  setInterval(function() {
    for (var k = 0; k < elements.length; k++) {
      angle += 6;
      elements[k].style.webkitTransform = 'rotateZ('+ angle +'deg)';
      elements[k].style.transform = 'rotateZ('+ angle +'deg)';
    }
  }, 1000);

The final, complete JS file should look like:

/*
 * Starts any clocks using the user's local time
 * From: cssanimation.rocks/clocks
 * Modified by: http://hackshop.org
 */
function setSec() {
  // Get the local time using JS
  var date = new Date;
  var seconds = date.getSeconds();
  var minutes = date.getMinutes();
  var hours = date.getHours();

  // Create variable with hour hand angle in degrees
  var angle = 6 * seconds;

  // set the angle of hour hand element
  var elements = document.querySelectorAll('.seconds');
  for (var k = 0; k < elements.length; k++) {
    elements[k].style.webkitTransform = 'rotateZ('+ angle +'deg)';
    elements[k].style.transform = 'rotateZ('+ angle +'deg)';
  }
   
  setInterval( function() {
    for (var k = 0; k < elements.length; k++) {
      angle += 6;
      elements[k].style.webkitTransform = 'rotateZ('+ angle +'deg)';
      elements[k].style.transform = 'rotateZ('+ angle +'deg)';
    }
  } , 1000);

}

You can break these new lines of code down to read as:

setInterval( function() {...} , 1000 );

Which means, repeat function() every 1,000 milliseconds, i.e. every second. The stuff inside of the {...} is just the previous rotateZ() commands, but there's something new in there: angle += 6;. Can you guess what this is doing? The += operator means "add this amount to the previous value". So then, by what amount are we changing the angle every second? Does that make sense?

STEP 3: Modify setMin and setHr Too

Got the hang of it? Next you'll need to animate the minute and hour hands too. Here's what you'll need to do:

  1. Copy/paste the new setInterval code from set-sec.js into the set-min.js and set-hr.js files;
  2. Replace '.seconds' class in the document.querySelectorAll('.hours'); to be '.minutes' or '.hours'
  3. Change the angle calculation in each setInterval block of the JS file to be correct for minutes and hours, not seconds;

Step 3 is a tricky one. In the set-sec.js file we use angle += 6 to change the angle of the second hand by 6° every second, but by how many degrees should the minute or hour hand change every second? I past challenges you've already calculated the deg/min or the deg/hr for the minute and hour hand, now you'll need to calculate the deg/sec for these hands too.

Once you've made all these changes, make sure to refresh your clock preview in order to see the functions go to work!

STEP 4: Fancy-up the Tick Tock

If everything's gone well at this point, you've got a ticking clock. Congratulations! Now we can get fancy.

There are all sorts of things you could do to customize your clock at this point. You can change colors, shapes, backgrounds, or anything else you'd like, but let's focus on the animation for just a second more.

You might have noticed that the ticking is well, a little boring. It's got no bounce to it. We can add a little life to this clock using an advanced CSS effect called transition. We're going to add the following line of code to your style.css file under the .seconds selector:

transition: transform 0.2s cubic-bezier(.4,2.08,.55,.44);

This section of your style.css file should now look like:

.clock .seconds {
  background: red;
  height: 45%;
  left: 49.5%;
  position: absolute;
  top: 14%;
  transform-origin: 50% 80%;
  width: 1%;
  transform: rotateZ(180deg);
  z-index: 3;
  transition: transform 0.2s cubic-bezier(.4,2.08,.55,.44);
}

Do you see a difference in the motion of the second hand now? It's got a nice natural bounce to it doesn't it? How did we do that? Let's unpack that little line of code.

The transition effect takes four arguments: property, duration, timing-function, and delay; In this case we're setting these values to be:

  • transition-property: transform
  • transition-duration: 0.2s
  • transition-timing-function: cubic-bezier(.4,2.08,.55,.44)

The transform property is what we've been setting using the rotateZ() all along. The cubic-bezier(.4,2.08,.55,.44) timing function is something interesting: it's what gives the hand that bounce. Click here to learn more and play around with the cubic bezier function. The 0.2s duration just makes the transition happen in 1/5th of a second.

STEP 5: Final Minute Touches

So your clock should be looking pretty fancy at this point, but you might be wondering about that minute hand. It's got no bounce and it seems to awkwardly creep along.

That's because we've been keeping the minute hand moving at a continuous pace using the decimal time by adding in the seconds. We can give the minute hand more of that clock feel by making some small adjustments.

Basically, we want the minute hand to bounce or tick every minute, not every second.

First, remove the seconds factor from the decimal time calculation in the set-min.js file. Basically, change this:

var angle = 6 * ( minutes + (seconds / 60) );

to this:

var angle = 6 * minutes;

Then you're gonna want to change the setInterval function in set-min.js to adjust the minute hand angle to change by 6° angle += 6 every 60 seconds, or 60,000 milliseconds.

Lastly, copy/paste that cubic-bezier transform line from the .seconds selector in your style.css file we adjusted in the previous step to the .minutes selector in that same style.css file.

One Final Complication

If you did all of that correctly, the minute hand should always point to a specific minute and only change every minute, not every second. However, because you setup the setMin JS function to start onload, i.e. when the page loads (see previous challenges), the minute hand will jump every 60 seconds after that initial page load moment.

So if that was halfway through a minute, say when the second hand is on the 6, then the minute hand will not change when the second hand reaches 12. It will change every time the second hand reaches 6 again. Ugh!!!

What we need to do is to delay the start of the setInterval function so that it starts counting at the beginning of the next minute. Luckily that's fairly easy to do using another function called setTimeout( cb, ms ).

This function delays the execution of some command cb until the ms milliseconds have expired. We're going to use this to adjust the set-min.js file one last time so that it now sets the initial time, waits until the current minute is over (i.e. when the second hand reaches the 12), then moves the minute hand and starts up the regular setInterval function from that moment so that the minute hand is synched up to the second hand at 12.

The final set-min.js code looks like this:

/*
 * Starts any clocks using the user's local time
 * From: cssanimation.rocks/clocks
 * Modified by: http://hackshop.org
 */
function setMin() {
  // Get the local time using JS
  var date = new Date;
  var seconds = date.getSeconds();
  var minutes = date.getMinutes();
  var hours = date.getHours();

  // Create variable with hour hand angle in degrees
  var angle = 6 * minutes;

  // set the angle of hour hand element
  var elements = document.querySelectorAll('.minutes');
  for (var k = 0; k < elements.length; k++) {
    elements[k].style.webkitTransform = 'rotateZ('+ angle +'deg)';
    elements[k].style.transform = 'rotateZ('+ angle +'deg)';
  }

  // Set a timeout until the end of the current minute, to move the hand
  var delay = (60.1 - seconds) * 1000;
  setTimeout(function() {
    for (var k = 0; k < elements.length; k++) {
      angle += 6;
      elements[k].style.webkitTransform = 'rotateZ('+ angle +'deg)';
      elements[k].style.transform = 'rotateZ('+ angle +'deg)';
    }
    setInterval(function() {
      for (var k = 0; k < elements.length; k++) {
        angle += 6;
        elements[k].style.webkitTransform = 'rotateZ('+ angle +'deg)';
        elements[k].style.transform = 'rotateZ('+ angle +'deg)';
      }
    }, 60000);
  }, delay);
}

STEP 6: Check & Share Your Work

If you want to make sure you're on the right track, you can check your code against this Thimble project.

When you are ready, share your clock on Edmodo.