Create a Totally Awesome Analog Clock

This is a companion discussion topic for the Creating an Awesome Analog Clock tutorial! :robot:

You can see a live version of the analog clock here: https://www.kirupa.com/html5/examples/awesome_analog_clock.htm

If you prefer a video, my tweet for this has it embedded:

Lastly, if you want the full code, take a look below:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Awesome Analog Clock</title>

  <style>
    body {
      background-color: #EEE;
    }

    .section {
      background-color: #FFF;
      padding: 20px;
      width: 550px;
      border-radius: 5px;
      margin: 0 auto;
      margin-top: 30px;

      display: grid;
      justify-content: center;
    }

    #analogClock {
      display: grid;
      justify-items: center;
      align-items: center;
    }

    #analogClock > * {
      grid-area: 1 / 1;
    }

    #analogClock .center {
      width: 10px;
      height: 10px;
      background-color: #E83151;
      border: 5px solid #FFF;
      border-radius: 50%;
      position: relative;
      z-index: 100;
    }

    #analogClock .second,
    #analogClock .hour,
    #analogClock .minute {
      box-shadow: 0 0 10px #222;
      position: relative;
      transform-origin: left;
      border-radius: 10px;
    }

    #analogClock .second {
      height: 0px;
      width: 140px;
      border-top: 2px solid #E83151;

      left: 70px;
      top: 0px;

      transform: rotate(var(--seconds));
    }

    #analogClock .hour {
      height: 0px;
      width: 75px;
      border-top: 8px solid #FFF;

      left: 40px;
      top: 0px;

      transform: rotate(var(--hours));
    }

    #analogClock .minute {
      height: 0px;
      width: 140px;
      border-top: 4px solid #FFF;

      left: 70px;
      top: 0px;

      transform: rotate(var(--minutes));
    }
  </style>
</head>

<body>
  <div class="section">
    <div id="analogClock">
      <svg width="448" height="448" xmlns="http://www.w3.org/2000/svg">
        <g fill="none" fill-rule="evenodd">
          <path fill="#FFF" d="M-9-9h501v501H-9z" />
          <g transform="translate(24 24)">
            <circle stroke="#0041C5" stroke-width="24" fill="#0054FF" stroke-linecap="square" cx="200" cy="200" r="212" />
            <g fill="#FFF" font-family="HelveticaNeue-Bold, Helvetica Neue, Arial, sans-serif" font-size="100"
              font-weight="bold" opacity=".9">
              <text transform="translate(34.943 23.908)">
                <tspan x="107.159" y="98">12</tspan>
              </text><text transform="translate(34.943 23.908)">
                <tspan x="264.614" y="218.46">3</tspan>
              </text><text transform="translate(34.943 23.908)">
                <tspan x="3.464" y="218.46">9</tspan>
              </text><text transform="translate(34.943 23.908)">
                <tspan x="136.798" y="336.161">6</tspan>
              </text></g>
            <g transform="translate(13.793 13.22)">
              <circle fill-opacity=".5" fill="#FFF" cx="3.678" cy="186.667" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="27.586" cy="96.552" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="95.632" cy="27.586" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="185.747" cy="3.678" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="276.782" cy="27.586" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="344.828" cy="96.552" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="368.736" cy="186.667" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="344.828" cy="277.701" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="276.782" cy="345.747" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="185.747" cy="369.655" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="95.632" cy="346.667" r="3.678" />
              <circle fill-opacity=".5" fill="#FFF" cx="27.586" cy="277.701" r="3.678" />
              <circle stroke="#0041C5" stroke-width="5" cx="186.667" cy="187.241" r="6.695" />
            </g>
          </g>
        </g>
      </svg>
      <div class="center"></div>
      <div class="second"></div>
      <div class="hour"></div>
      <div class="minute"></div>
    </div>
  </div>

  <script>
    let clockElement = document.querySelector("#analogClock");
    let offset = -90;

    let reducedMotion = false;

    function timer() {
      let date = new Date();

      let milliseconds = date.getMilliseconds();
      let seconds = date.getSeconds();
      let hours = date.getHours();
      let minutes = date.getMinutes();

      seconds += milliseconds / 1000;
      minutes += seconds / 60;
      hours += minutes / 60;

      // Normalize to the 12 hour clock
      if (hours > 12) {
        hours -= 12;
      }

      clockElement.style.setProperty("--seconds", offset + 6 * seconds + "deg");
      clockElement.style.setProperty("--hours", offset + 30 * hours + "deg");
      clockElement.style.setProperty("--minutes", offset + 6 * minutes + "deg");

      requestAnimationFrame(timer);
    }
    timer();

    let reduceMotionQuery = matchMedia("(prefers-reduced-motion)");

    function setAccessibilityState() {
      if (reduceMotionQuery.matches) {
        reducedMotion = true;
      } else { 
        reducedMotion = false;
      }
    }
    setAccessibilityState();

    reduceMotionQuery.addListener(setAccessibilityState);
  </script>
</body>

</html>

As always, if you have any questions about the code or suggestions on how to make it better, let me know.

I am working on step-by-step tutorial on this right now, so expect to see that live in a few days-ish.

Cheers,
Kirupa

2 Likes

And, the tutorial is up: https://www.kirupa.com/html5/totally_awesome_analog_clock.htm

:stuck_out_tongue:

1 Like

Thanks :slight_smile: finally got around to running through this and learnt quite a few things, like actually making CSS variables and also a bit of SVG which I have never actually used before.

I couldn’t help myself but to make 3 obsessive changes in my code to help me sleep at night:

  1. I transferred all the SVG transforms into the element positions themselves:
<text>
    <tspan x="142.102" y="121.908">12</tspan>
</text>

etc.

  1. I changed the order of the CSS so that it was second, minutes, hours rather than seconds, hours, minutes… I got problems!
  2. This is my most pointless tweak, I saw that the centre point edges of the hour hand was not quite in line so I changed its width to be even pixels and then halved them to get the left position. This had no practical benefit because you put the circle over the hour hand at the end :man_facepalming:

But with all of that, this helped me just get some more practise with modern CSS, which I really appreciate. And I wouldn’t have thought to create SVGs with a designer tool, I didn’t have a clue how to create them before.

1 Like

Hi @mungojam - welcome to the forums :stuck_out_tongue:

That was a really nice summary of your experiences. I should fix the alignment of the hour hand. I missed it the first time around, but now that you have pointed it out, I can’t unsee it haha.

For the ordering between seconds, minutes, and hours, I will admit that I spent more than a few minutes figuring out the best order to put them in. I also wasn’t sure if it needed to be plural or singular. I also debated with myself on whether the word hand needed to be there in the CSS as well.

Whatever problem you have, I have it as well haha :joy:

Thanks for the feedback! If you have another idea for a larger end-to-end example, do let me know. These are more time consuming to write, but they are valuable in showing how all the pieces fit together.

Cheers,
Kirupa

1 Like

The accessibility bit was great too. I mostly make internal work sites so the emphasis isn’t so much but by being ‘forced’ to do it to complete a tutorial meant that I saw how easy it was and then stumbled upon the colour blind emulation in chrome which I’ll definitely use in future and big-up at work.

I’ve been trying to think of ideas. Most involve external things like material UI or rxjs that people rave about but I haven’t got around to using. One more native web thing would be something fun with the webcam API. Or something more advanced like webgl, web workers or indexed dB. But again it’s just laziness or lack of time that means I haven’t played with them yet. And it might be hard to make a fun tutorial for them.

By the way I used the popout panel from your react book / tutorial for something at work 2 years ago and still going strong :slightly_smiling_face:. My css usually needs inspiration like that

1 Like