Home > Net >  Javascript Stopwatch being slower than expected
Javascript Stopwatch being slower than expected

Time:09-17

So... in my quest to become a Javascript developer and as an F1 fan, i had to make a stopwatch, to track reaction time. The problem i stumbled upon, and it seems to be the case in many tutorials, i ve seen, is that, you can't really be millisecond exact. What i mean is, when i try to set an Interval for every 10 milliseconds, it works perfect but if i try to set an Interval for 1 millisecond, 1 second on my page is like 5 seconds in real life.

let clock = document.querySelector('.timer')
let interval = null;
let milliseconds = 0;
let seconds =0;



window.addEventListener('click', () => {
        interval = setInterval(startClock,10)
})


document.addEventListener('keyup', event => {
    if (event.code === 'Space') {
        clearInterval(interval);
        setTimeout(()=>{
            alert(`${seconds}:${milliseconds}`);
        },50) 
    }
})

function startClock(){
    milliseconds  = 10;
    if(milliseconds==1000){
        milliseconds = 0;
        seconds  ;
    }

    let sec = seconds.toString();
    let ms = milliseconds.toString();
    if(seconds<10){
        sec = `0${seconds}`;
    }

    if(milliseconds<100){
        ms = `0${milliseconds}`;
    }

    clock.innerHTML =`${sec}:${ms}`;
}
p{
    color: rgb(25, 25, 25);
    font-size: 170px;
    height: 120px;
    font-weight: bold;
}
<p >00:000</p>

CodePudding user response:

for every 10 milliseconds, it works perfect

Even then it's not reliable. It can drift over time, it can be delayed by anything blocking the UI even for the briefest of moments, etc.

Taking a step back... Do you really need your UI clock to display every individual millisecond? Are you observing the results 1,000 times per second? Motion pictures update a couple dozen times per second and we perceive them as fluid continuous motion. What human needs to see 1,000 distinct numbers per second?

Instead, have your clock update the UI at regular intervals (if every 10ms is working for you then that's reasonable, every 50ms would probably be reasonable too) to show the current time. You don't need to create a stopwatch to measure time, the computer is already measuring time and you can query that measurement whenever you like, as often as you like.

All your "stopwatch" needs to do is know when it started and it can always calculate the elapsed milliseconds since then.

You can perhaps try to mimic the more random look and feel of a quickly-ticking millisecond timer by making the intervals an odd number, like 27ms or 41ms.

For example:

let clock = document.querySelector('.timer')
let interval = null;
let startTime = null;

window.addEventListener('click', () => {
  startTime = new Date();
  interval = setInterval(clockTick, 42)
})

function clockTick(){
  let diff = new Date().getTime() - startTime.getTime();
  clock.innerHTML =`${Math.floor(diff / 1000)}:${`${diff % 1000}`.padStart(3, "0")}`;
}
p{
    color: rgb(25, 25, 25);
    font-size: 170px;
    height: 120px;
    font-weight: bold;
}
<p >00:000</p>

CodePudding user response:

Since you can't guarantee how long the code inside the interval will take to run, it's better to use the Date api. Instead of relying on the interval, you could run a loop that constantly calculates the ms between now and the start.

Here's an example of this concept:

// assume this runs when the stopwatch starts
let startTime = new Date().getTime()

// this should stop when the user stops the timer, but I'm using a for loop
// for the example
for(let i = 0; i < 100; i  ) {
  let now = new Date().getTime()
  console.log("ms elapsed: "   (now - startTime))
}

Now it doesn't matter how fast the code is; it should always be accurate.

CodePudding user response:

Javascript is single-threaded, which means it can only run one task at a time, blocking everything else.

That's why the browser stops working if you do while(true) {}. You can't scroll, click, and all animations stop while the loop runs.

Instead, I would suggest to use requestForAnimationFrame, so the timer only updates when the screen does a repaint (60, 120, 144 or whatever Hz your monitor has).

My timer stops when you click a second time.

const timerEl = document.getElementById('timer');
var startTime = 0, animationId = 0;

const toggleTimer = () => {
  if (startTime == 0) {
    startTime = new Date().getTime();
    startTimer();
  } else {
    stopTimer();
    updateTimerElement(new Date().getTime());
    startTime = 0;
  }
}

const startTimer = () => {
  updateTimerElement(new Date().getTime());
  animationId = requestAnimationFrame(startTimer);
}

const stopTimer = () => {
  cancelAnimationFrame(animationId);
}

const updateTimerElement = (currentTime) => {
  let passedTime = currentTime - startTime;
  const MINUTE = 1000 * 60;
  const SECOND = 1000;
  
  let min = Math.floor(passedTime / MINUTE);
  
  passedTime = passedTime - min * MINUTE;

  let sec = Math.floor(passedTime / SECOND);
  
  let ms = passedTime - sec * SECOND;
    
  timerEl.innerText = `${addPadding(min)}:${addPadding(sec)}:${addPadding(ms, 3)}`;
}

const addPadding = (number, pad = 2) => {
  return String(number).padStart(pad, '0');
}

document.addEventListener('mouseup', () => { toggleTimer() })
body {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  padding: 0;
}

#timer {
  font-size: 40vh;
  font-weight: bold;
}
<div id="timer">00:00:000</div>

  • Related