0

I have a stopwatch for a small tool here, I received most of the code from a previous question and I was going about trying to implement it, I began breaking it down and trying to understand it.

So far I think I understand most of it (Still some bits I am researching); however I was trying to adapt the code to my tool.

My requirements:

  • A start/stop button (a single button) - the value will change depending on if the timer is running or not.

A reset button - this will simply reset the timer to 00:00:00 and if the tool is running it will also stop it.

So far, the reset button is not configured, this is fine. The start and stop button works; however say I stopped the timer, and then started it again without resetting it, the timer just begins at 00:00:00 again, it will not continue from where it was paused.

It would be greatly appreciated if anyone would be able to explain how I could do this? I have tried the following:

Storing 'differenceInMillis for each loop of updateTimer() in a global variable, then subtracting the value from startTime = Date.now() each time the timer is restarted (This was suggested by a user in a previous question), I could not get this to work.

The code I have so far - HTML (buttons and clock):

const outputElement = document.getElementById("outputt");
var startTime = 0;
var running = 0;
var splitcounter = 0;


function startstop() {
  if (running == 0) {
    running = 1;
    startTime = Date.now();
    startstopbutton.value = 'Stop';
    document.getElementById("outputt").style.backgroundColor = "#2DB37B";
    updateTimer();
  } else {
    running = 0;
    // logTime();
    startstopbutton.value = 'Start';
    document.getElementById("outputt").style.backgroundColor = "#B3321B";
  }
}

function updateTimer() {
  if (running == 1) {
    let differenceInMillis = Date.now() - startTime;
    let {
      hours,
      minutes,
      seconds
    } = calculateTime(differenceInMillis);
    let timeStr = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;

    outputElement.innerText = timeStr;
    requestAnimationFrame(updateTimer);
  }
}

function calculateTime(milliS) {
  const SECONDS = 1000; // should be 1000 - only 10 to speed up the timer
  const MINUTES = 60;
  const HOURS = 60;
  const RESET = 60;

  let hours = Math.floor(milliS / SECONDS / MINUTES / HOURS);
  let minutes = Math.floor(milliS / SECONDS / MINUTES) % RESET;
  let seconds = Math.floor(milliS / SECONDS) % RESET;

  return {
    hours,
    minutes,
    seconds
  };
}

function pad(time) {
  return time.toString().padStart(2, '0');
}
<input id="startstopbutton" class="buttonZ" style="width: 120px;" type="button" name="btn" value="Start" onclick="startstop();">
<input id="resetbutton" class="buttonZ" style="width: 120px;" type="button" name="btnRst" id='btnRst' value="Reset" onclick="resetclock();" />
<div id="outputt" class="timerClock" value="00:00:00">00:00:00</div>

UPDATE new version - does not work however - session storage does not work at SO so will have to test elsewhere

const outputElement = document.getElementById("outputt");
var startTime = 0;
var running = 0;
var splitcounter = 0;


function startstop() {
  if (running == 0) {
    running = 1;
    startTime = new Date(sessionStorage.getItem("time"))
    if (isNaN(startTime)) startTime = Date.now();
    startstopbutton.value = 'Stop';
    document.getElementById("outputt").style.backgroundColor = "#2DB37B";
    updateTimer();
  } else {
    running = 0;
    logTime();
    startstopbutton.value = 'Start';
    document.getElementById("outputt").style.backgroundColor = "#B3321B";
  }
}

function updateTimer() {
  if (running == 1) {
    let differenceInMillis = Date.now() - startTime;
    sessionStorage.setItem("time", differenceInMillis)
    let {
      hours,
      minutes,
      seconds
    } = calculateTime(differenceInMillis);
    let timeStr = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;

    outputElement.innerText = timeStr;
    requestAnimationFrame(updateTimer);
  }
}

function calculateTime(milliS) {
  const SECONDS = 1000; // should be 1000 - only 10 to speed up the timer
  const MINUTES = 60;
  const HOURS = 60;
  const RESET = 60;

  let hours = Math.floor(milliS / SECONDS / MINUTES / HOURS);
  let minutes = Math.floor(milliS / SECONDS / MINUTES) % RESET;
  let seconds = Math.floor(milliS / SECONDS) % RESET;

  return {
    hours,
    minutes,
    seconds
  };
}

function pad(time) {
  return time.toString().padStart(2, '0');
}
mplungjan
  • 169,008
  • 28
  • 173
  • 236
srcomptn
  • 81
  • 1
  • 8
  • 1
    Please use booleans: `if (running) { running = false;` .... `running = true` – mplungjan Sep 30 '20 at 08:43
  • 1
    I made you a snippet and commented out the missing logtime – mplungjan Sep 30 '20 at 08:44
  • 1
    You have `startTime = Date.now();` - you need to test if you are sure you want to reset that – mplungjan Sep 30 '20 at 08:45
  • @BadHorsie I am intending on avoiding jQuery and such, this is mostly because I would prefer to learn pure JS initially. – srcomptn Sep 30 '20 at 09:00
  • 1
    You cannot make this work by updating your timer with the difference between the start time and now. When you click start, the difference between that moment and now will always be 0 (the line `startTime = Date.now()` is the issue) and so your timer shows 0 again. – BadHorsie Sep 30 '20 at 09:01
  • 1
    You don't need jQuery. See here https://stackoverflow.com/a/56746381/851885 – BadHorsie Sep 30 '20 at 09:01
  • @mplungjan the snippet still resets the clock on continuing,also I am unsure why booleans would be required here, the method used works as intended for this purpose. – srcomptn Sep 30 '20 at 09:01
  • 1
    You can use the 3rd answer which @BadHorsie has suggest, it's a good practice – Abhishek Pandey Sep 30 '20 at 09:04
  • @BadHorsie the method used in that link is inaccurate, I have a second timer which uses the same method all working, I found it is hugely inaccurate and was also told this by multiple others on here, please see:https://stackoverflow.com/questions/64120673/javascript-clock-counts-past-60-for-minutes-and-seconds – srcomptn Sep 30 '20 at 09:05
  • @SeanCompton Yes, I know - this is common knowledge. Making stopwatch scripts has been a JS learning exercise for a couple of decades now. I was just pointing out that you don't need jQuery and that your logic using the current date will not work with a stop/start button because the 'current time' is always running and doesn't stop just because you clicked your button. You need to keep track only of the time elapsed while your clock is running. – BadHorsie Sep 30 '20 at 09:15

1 Answers1

2

If you MUST use a date object, you will need to change

startTime = Date.now();

to

startTime = new Date(sessionStorage.getItem("time")) 
if (isNaN(startTime)) startTime = Date.now();

and save the time

let differenceInMillis = Date.now() - startTime;
sessionStorage.setItem("time",differenceInMillis) 

If not, use a counter instead of a date object

Also when you use a toggle as boolean, make it and use it as a boolean (good practice - not mandatory for this issue)

const outputElement = document.getElementById("outputt");
let counter = 0,
    running = false,
    splitcounter = 0,
    lastTime = 0;

function startstop() {
  running = !running;
  startstopbutton.value = running ? 'Stop' : 'Start';
  document.getElementById("outputt").style.backgroundColor = running ? "#2DB37B" : "#B3321B";
  if (running) updateTimer(0)
}



function updateTimer(currentTime) {
  if (running) requestAnimationFrame(updateTimer)
  if (currentTime >= (lastTime + 1000)) {
    counter++;
    lastTime = currentTime;
    let {
      hours,
      minutes,
      seconds
    } = calculateTime(counter * 1000);
    let timeStr = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
    outputElement.innerText = timeStr;
  }
}

function calculateTime(milliS) {
  const SECONDS = 1000; // should be 1000 - only 10 to speed up the timer
  const MINUTES = 60;
  const HOURS = 60;
  const RESET = 60;

  let hours = Math.floor(milliS / SECONDS / MINUTES / HOURS);
  let minutes = Math.floor(milliS / SECONDS / MINUTES) % RESET;
  let seconds = Math.floor(milliS / SECONDS) % RESET;

  return {
    hours,
    minutes,
    seconds
  };
}

function pad(time) {
  return time.toString().padStart(2, '0');
}
<input id="startstopbutton" class="buttonZ" style="width: 120px;" type="button" name="btn" value="Start" onclick="startstop();">
<input id="resetbutton" class="buttonZ" style="width: 120px;" type="button" name="btnRst" id='btnRst' value="Reset" onclick="resetclock();" />
<div id="outputt" class="timerClock" value="00:00:00">00:00:00</div>
mplungjan
  • 169,008
  • 28
  • 173
  • 236
  • 1
    Using artificial counter is much better idea than struggling with real date time until it's necessary. – Abhishek Pandey Sep 30 '20 at 09:10
  • Thank you for this, I will have to practise using booleans more. - Regarding using a counter, this is typically very inaccurate, I have found with previous timer code I have used. That is why I would like to use real date time to accurately time tasks. – srcomptn Sep 30 '20 at 09:15
  • @mplungjan please see my edit, I am going to look into this regardless, I would like to understand it and be able to see where I was going wrong myself; however your help has been much appreciated. – srcomptn Sep 30 '20 at 09:33
  • 1
    @SeanCompton If you're using a 0/1 switch you're using a boolean. Just use `true` or `false` keywords instead, that's all there is to it. Using 0 or 1 implies your variable could also have a value of 2, 3, or 893456. – BadHorsie Sep 30 '20 at 09:33
  • 1
    @SeanCompton I did not test my suggestion with the session storage. Note snippets with web storage do not work at SO – mplungjan Sep 30 '20 at 09:39
  • Thank you @mplungjan - I will attempt to implement booleans now, also I have tested the session storage within google chrome, the issue is now that when restarted the timer sets to something rediculous such as '-19330839:-3:-53' that is what is currently displayed. – srcomptn Sep 30 '20 at 10:04
  • 1
    Ok, that looks like the diff is negative. I am sorry I really do not have time today – mplungjan Sep 30 '20 at 10:06