I've built a simple chronometer but I want it to be as fast and accurate as possible. In other words, I want the best refresh rate while keeping its precision and by precision I mean the digits for the seconds should change every 1000 ms, the minutes should change every 60 seconds, etc.
The chronometer itself was pretty easy to make but I get weird behaviours when I try to make it fast.
At first, the chronometer was just stuck at 00:00 and it would not even change. But I quickly realized it was because I wasn't giving the browser any time to render the element between every execution of the function that updates and formats the time. So I tried to make it so every next update is made after a 0 ms timeout. This way, I thought it would put the execution of the next update at the end of the execution stack so the browser would have rendered the element before the next update function is executed.
The timeout fixed my issue but only partially. The chronometer seemed to be working fine until I realized that a second was skipped every so often.
I tried adding a small delay of like 5 ms to the timeout but seconds were still skipped though the delay between every skipped seconds was larger. I tried to play with the delay of the timeout but seconds are still skipped even with a delay of over 50 ms. I even tried to use the queueMicrotask function and combine it with the timeout but it didn't help.
At this point, I tried to find another solution but I couldn't find anything. I don't understand why this is happening so I would really appreciate if anyone could explain me what's going on and how I can fix this.
Note: the delay between each skipped seconds is not constant and for some reason, seconds are skipped more often after 1 minute.
The chronometer
const START_TIME = Date.now();
function formatTime(ms) {
ms = Math.round(ms * 1);
let secs = Math.trunc(ms / 1e3);
ms = (ms / 1e3 - secs) * 1e3;
let mins = Math.trunc(secs / 60);
secs = (secs / 60 - mins) * 60;
let hours = Math.trunc(mins / 60);
mins = (mins / 60 - hours) * 60;
hours = String(Math.trunc(hours));
mins = String(Math.trunc(mins));
secs = String(Math.trunc(secs));
ms = String(Math.trunc(ms));
const pad = (t, i = 2) => t.padStart(i, "0");
return `${pad(hours)}:${pad(mins)}:${pad(secs)},${pad(ms, 3)}`;
}
(() => {
const el = document.querySelector('.time');
const update = () => {
el.innerText = formatTime(Date.now() - START_TIME);
setTimeout(update);
}
update();
})();
@import url("https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap");
body {
background-color: #222;
color: #35E;
margin: 0;
padding: 0;
}
.wrapper {
font-family: "Inconsolata", monospace;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
font-size: calc(4vw + 4vh + 10vmin);
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
}
<div class="wrapper">
<div class="time"></div>
</div>
Update:
I tried with `requestAnimationFrame` and I still get the same result.