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
And, the tutorial is up: https://www.kirupa.com/html5/totally_awesome_analog_clock.htm
Thanks 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:
- I transferred all the SVG transforms into the element positions themselves:
<text>
<tspan x="142.102" y="121.908">12</tspan>
</text>
etc.
- I changed the order of the CSS so that it was second, minutes, hours rather than seconds, hours, minutesā¦ I got problems!
- 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
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.
Hi @mungojam - welcome to the forums
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
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
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 . My css usually needs inspiration like that
Great, Thank you for your contribution. Create A Digital Clock in HTML CSS and JS
Welcome to the forums, @Prince_Rauf! Nice job there
For another way to build it, here is my version:
Another minimal implementation with 3 lines of JS.
<style>
clock-face, clock-face::after, hour-hand, minute-hand, second-hand {
display: block;
transform-origin: 50% 100%;
position: absolute;
border-radius: 200px;
}
hour-hand, minute-hand, second-hand {
animation: linear infinite rotate;
background: black;
height: 90px;
top: 10px;
}
clock-face {
width: 200px;
height: 200px;
border: 10px solid black;
}
clock-face::after {
content: "";
width: 20px;
height: 20px;
background: black;
top: 90px;
left: 90px;
}
hour-hand {
width: 15px;
height: 60px;
top: 40px;
left: 92.5px;
animation-duration: 43200s;
}
minute-hand {
width: 10px;
left: 95px;
animation-duration: 3600s;
}
second-hand {
background: red;
width: 5px;
left: 97.5px;
animation-duration: 60s;
}
@keyframes rotate {
from { rotate: 0; }
to { rotate: 360deg; }
}
</style>
<clock-face>
<hour-hand></hour-hand>
<minute-hand></minute-hand>
<second-hand></second-hand>
</clock-face>
<script>
document.querySelector("second-hand").style.animationDelay = `-${new Date().getSeconds()}s`;
document.querySelector("minute-hand").style.animationDelay = `-${new Date().getMinutes() * 60}s`;
document.querySelector("hour-hand").style.animationDelay = `-${(new Date().getHours() % 12) * 3600}s`;
</script>