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