Skip to content Skip to sidebar Skip to footer

How Can I Fix The Stop-start Process Within This Javascript Stopwatch-clock?

I have a JavaScript stopwatch here, I require the start-stop button to keep the same time when continuing. Currently, if I stop and continue the clock diff is something ridiculous

Solution 1:

Issue with your code:

  • You start with initial value for sessionStorage as Date.now but then save difference on update.
  • You interact a lot with session storage. Any communication with external API is expensive. Instead use local variables and find an event to initialise values.
  • Time difference logic is a bit off.
    • Date.now - startTime does not considers the difference between stop action and start action.
    • You can use this logic: If startTime is defined, calculate difference and add it to start time. If not, initialise it to Date.now()

Suggestions:

  • Instead of adding styles, use classes. That will help you in reset functionality
  • Define small features and based on it, define small functions. That would make reusability easy
  • Try to make functions independent by passing arguments and only rely on them. That way you'll reduce side-effect

Note: as SO does not allow access to Session Storage, I have removed all the related code.

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

function logTime() {
  console.log('Time: ', lastTime)
}

function resetclock() {
  running = false;
  startTime = 0;
  printTime(Date.now())
  applyStyles(true)
}

function applyStyles(isReset) {
  startstopbutton.value = running ? 'Stop' : 'Start';
  document.getElementById("outputt").classList.remove('red', 'green')
  if (!isReset) {
    document.getElementById("outputt").classList.add(running ? 'red' : 'green')
  }
}
function startstop() {
  running = !running;
  applyStyles();
  if (running) {
    if (startTime) {
      const diff = Date.now() - lastTime;
      startTime = startTime + diff;
    } else {
      startTime = Date.now()
    }
    
    updateTimer(startTime);
  } else {
    lastTime = Date.now()
    logTime();
  }
}

function printTime(startTime) {
  let differenceInMillis = Date.now() - startTime;
  let {
    hours,
    minutes,
    seconds
  } = calculateTime(differenceInMillis);
  let timeStr = `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
  outputElement.innerText = timeStr;
}

function updateTimer(startTime) {
  if (running == true) {
    printTime(startTime)
    requestAnimationFrame(() => updateTimer(startTime));
  }
}

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');
}
.red {
  background-color: #2DB37B
}

.green {
  background-color: #B3321B
}
<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="btnRst1" id='btnRst1' value="Reset" onclick="resetclock();" />
<div id="outputt" class="timerClock" value="00:00:00">00:00:00</div>

Solution 2:

simple stopwatch example

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
<input class="startstop" style="width: 120px;" type="button" value="Start" onclick="startstop();">
<input class="reset" style="width: 120px;" type="button" value="Reset" onclick="reset();"/>   
<div class="timerClock" value="00:00:00">00:00:00</div>
<script type="text/javascript">
    var second = 0
    var minute = 0
    var hour = 0
    var interval
    var status = false
    var element = document.querySelector('.startstop')
    var clock = document.querySelector('.timerClock')
    var string = ''
    function startstop() 
    {
        if(status == 'false')
        {
            element.value = 'Stop'
            clock.style.backgroundColor = "#2DB37B";
            status = true
            interval = setInterval(function()
            {
                string = ''
                second += 1
                if(second >= 60)
                {
                    minute += 1
                    second = 0
                }
                if(minute >= 60)
                {
                    hour += 1
                    minute = 0
                }

                if(hour < 10)
                    string += `0${hour}:`
                else
                    string += `${hour}:`

                if(minute < 10)
                    string += `0${minute}:`
                else
                    string += `${minute}:`

                if(second < 10)
                    string += `0${second}`
                else
                    string += `${second}`

                clock.innerHTML = string
            },1000)         
        }
        else
        {
            clock.style.backgroundColor = "#B3321B";
            element.value = 'Start'
            status = false
            clearInterval(interval)
        }
    }
    function reset()
    {
        second = 0
        minute = 0
        hour = 0
        status = false
        element.value = 'Start'
        clearInterval(interval)
        clock.innerHTML = `00:00:00`
        clock.style.backgroundColor = "transparent";
    }
</script>
</body>
</html>

Solution 3:

One thing to know about requestAnimationFrame is that it returns an integer that is a reference to the next animation. You can use this to cancel the next waiting animation with cancelAnimationFrame.

As mentioned by @Rajesh, you shouldn't store the time each update, as it will stop the current process for a (very) short while. Better in that case to fire an event, preferably each second, that will wait until it can run. I haven't updated the code to take that into account, I only commented it away for now.

It's also better to use classes than updating element styles. I wrote sloppy code that overwrites all classes on the #outputt element (it's spelled "output"). That's bad programming, because it makes it impossible to add other classes, but it serves the purpose for now. @Rajesh code is better written for this purpose.

I added two variables - diffTime and animationId. The first one corrects startTime if the user pauses. The second one keeps track if there is an ongoing timer animation.

I refactored your style updates into a method of its own. You should check it out, because it defines standard values and then changes them with an if statement. It's less code than having to type document.getElementById("outputt").style... on different rows.

I also added a resetclock method.

const outputElement = document.getElementById("outputt");
var startTime = 0;
var diffTime = 0;
var animationId = 0;

function startstop() {
  const PAUSED = 0;
  let paused = animationId == PAUSED;
  //diffTime = new Date(sessionStorage.getItem("time")) || 0;
  startTime = Date.now() - diffTime;

  if (paused) {
    updateTimer();
  } else {
    cancelAnimationFrame(animationId);
    animationId = PAUSED;
  }  

  updateTimerClass(paused);
}

function updateTimerClass(paused) {
  var outputClass = 'red';
  var buttonText = 'Start';
  
  if (paused) {
    outputClass = 'green';
    buttonText = 'Stop';
  } 
  
  startstopbutton.value = buttonText;
  outputElement.classList = outputClass;
}

function updateTimer() {
  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;
  diffTime = differenceInMillis;
  animationId = 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');
}

function resetclock() {
  let paused = animationId == 0;
  
  startTime = Date.now();
  diffTime = 0;
  
  if (paused) {
    const REMOVE_ALL_CLASSES = '';
    outputElement.className = REMOVE_ALL_CLASSES;
    outputElement.innerText = '00:00:00';
  }
}
#outputt.green {
  background-color: #2DB37B;
}

#outputt.red {
  background-color: #B3321B;
}
<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="btnRst1" id='btnRst1' value="Reset" onclick="resetclock();"/>   
<div id="outputt" class="timerClock" value="00:00:00">00:00:00</div>

Solution 4:

class Stopwatch {
    constructor(display, results) {
        this.running = false;
        this.display = display;
        this.results = results;
        this.laps = [];
        this.reset();
        this.print(this.times);
    }
    
    reset() {
        this.times = [ 0, 0, 0 ];
    }
  click(){
    var x=document.getElementById('ctrl');
    if(x.value=="start"){
     this.start();
     x.value="stop";
     document.getElementById("outputt").style.backgroundColor = "#2DB37B";
    }
    else{
     x.value="start";
     this.stop();
     document.getElementById("outputt").style.backgroundColor = "#B3321B";
    }
  }
    start() {
        if (!this.time) this.time = performance.now();
        if (!this.running) {
            this.running = true;
            requestAnimationFrame(this.step.bind(this));
        }
    }
    
    stop() {
        this.running = false;
        this.time = null;
    }

    resets() {
        document.getElementById("outputt").style.backgroundColor = "#2DB37B";
        if (!this.time) this.time = performance.now();
        if (!this.running) {
            this.running = true;
            requestAnimationFrame(this.step.bind(this));
        }
        this.reset();
    }
    
    
    step(timestamp) {
        if (!this.running) return;
        this.calculate(timestamp);
        this.time = timestamp;
        this.print();
        requestAnimationFrame(this.step.bind(this));
    }
    
    calculate(timestamp) {
        var diff = timestamp - this.time;
        // Hundredths of a second are 100 ms
        this.times[2] += diff / 1000;
        // Seconds are 100 hundredths of a second
        if (this.times[2] >= 100) {
            this.times[1] += 1;
            this.times[2] -= 100;
        }
        // Minutes are 60 seconds
        if (this.times[1] >= 60) {
            this.times[0] += 1;
            this.times[1] -= 60;
        }
    }
    
    print() {
        this.display.innerText = this.format(this.times);
    }
    
    format(times) {
        return `\
${pad0(times[0], 2)}:\
${pad0(times[1], 2)}:\
${pad0(Math.floor(times[2]), 2)}`;
    }
}

function pad0(value, count) {
    var result = value.toString();
    for (; result.length < count; --count)
        result = '0' + result;
    return result;
}

function clearChildren(node) {
    while (node.lastChild)
        node.removeChild(node.lastChild);
}

let stopwatch = new Stopwatch(
    document.querySelector('.stopwatch'),
    document.querySelector('.results'));
<input  type="button" id="ctrl" value="start" onClick="stopwatch.click();">
<input  type="button" value="Reset" onClick="stopwatch.resets();">
<div id="outputt"  class="stopwatch"></div>

Post a Comment for "How Can I Fix The Stop-start Process Within This Javascript Stopwatch-clock?"