DEV Community

Cover image for stopwatch using javascript - accurate and easy
Chris
Chris

Posted on • Edited on

stopwatch using javascript - accurate and easy

Overview

On a recent project, I needed to create a stopwatch. My initial thought was just to run javascript setInterval to keep track of time. While setInterval will be useful in our case, I made the mistake of depending on the setInterval alone to estimate time. Here is an example of what my code looked like in the beginning.

let totalSeconds = 0
setInterval(() => {
   totalSeconds += 1
}, 1000)  
Enter fullscreen mode Exit fullscreen mode

The problem was that the code above is not a good way to keep track of time. Because, even though our setInterval will call our callback function every second, it won't always execute the function at exactly one-second intervals. This is because our callback function will only be added to our call stack, but if the call stack has other work to execute, it will cause a delay in our time. Instead, let's build a more accurate stopwatch using javascript Date() constructor.

HTML and CSS

First, lets create a canvas we can work on. Here are the HTML and CSS used for this exercise.

HTML

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title></title>
    <meta name="description" content="">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="/styles.css">
  </head>
  <body>

    <div class="stopwatch-wrapper">
      <p class="stopwatch">00:00:00:00</p>
      <div class="control-buttons-wrapper">
        <button id="main-button" class="control-buttons">Start</button>
        <button id="clear-button" class="control-buttons">Clear</button>
      </div>
    </div>
    <script src="/stopwatch.js" ></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

CSS

* {
  font-family: sans-serif;
}

.stopwatch-wrapper {
  display: flex;
  justify-content: center;
  flex-direction: column;
  margin: 100px auto 0;
  width: fit-content;
  padding: 10px;
  box-shadow: 0 0px 2.2px rgba(0, 0, 0, 0.031), 0 0px 5.3px rgba(0, 0, 0, 0.044),
    0 0px 10px rgba(0, 0, 0, 0.055), 0 0px 17.9px rgba(0, 0, 0, 0.066),
    0 0px 33.4px rgba(0, 0, 0, 0.079), 0 0px 80px rgba(0, 0, 0, 0.11);
  border-radius: 5px;
}

.stopwatch {
  margin: 0 auto;
  text-align: center;
  font-size: 60px;
}

.control-buttons-wrapper {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
}
.control-buttons-wrapper button {
  outline: none;
  cursor: pointer;
  color: #fff;
  border: none;
  border-radius: 5px;
  font-size: 25px;
  margin: 0 10px;
  padding: 3px 8px;
}
.control-buttons-wrapper button:active {
  opacity: 0.7;
}
#clear-button {
  background: rgb(187, 187, 187);
}
#main-button {
  background: rgb(0, 146, 231);
}

Enter fullscreen mode Exit fullscreen mode

Writing our program

We will break down our program into four steps

  1. Creating our variables
  2. Add event listener to our buttons
  3. Creating our stopwatch function
  4. Creating a function to display our time to the DOM

So let's get started!

1.Creating our variables

create our constants variables to store the elements we'll be using. And an additional object variable called stopwatch to store all data required for our program.


const time = document.querySelector('.stopwatch')
const mainButton = document.querySelector('#main-button')
const clearButton = document.querySelector('#clear-button')
const stopwatch = { elapsedTime: 0 }
Enter fullscreen mode Exit fullscreen mode

2. Adding Event Listiners to buttons

for our main button we will create a condition based on the text of the button. If the user clicks our mainButton with Start text; we will call our startSTopwatch() function and update the button text. Else we will keep track of our elasped time in stopwatch.elapsedTime and stop our stopwatch interval.

mainButton.addEventListener('click', () => {
  if (mainButton.innerHTML === 'Start') {
    startStopwatch();
    mainButton.innerHTML = 'Stop'
  } else {
    stopwatch.elapsedTime += Date.now() - stopwatch.startTime
    clearInterval(stopwatch.intervalId)
    mainButton.innerHTML = 'Start'
  }
})
Enter fullscreen mode Exit fullscreen mode

Our second event listener will trigger when our clear button is clicked.

clearButton.addEventListener('click', () => {
  stopwatch.elapsedTime = 0
  stopwatch.startTime = Date.now()
  displayTime(0, 0, 0, 0)
})
Enter fullscreen mode Exit fullscreen mode

3. Creating our stopwatch function

Here is our stopwatch function. I've added inline comments for a better explanation.

Instead of relying solely on the setInterval(), we are comparing the start time and calculation the difference based on the current time. I have set the interval to 100 milliseconds, but you may change this if you wish. If accuracy is not an issue, you can increase the interval to 1,000; otherwise, the shorter the intervals, the more accurate your time records will be. I

function startStopwatch() {
  //reset start time
  stopwatch.startTime = Date.now();
  // run `setInterval()` and save the ID
  stopwatch.intervalId = setInterval(() => {
    //calculate elapsed time
    const elapsedTime = Date.now() - stopwatch.startTime + stopwatch.elapsedTime
    //calculate different time measurements based on elapsed time
    const milliseconds = parseInt((elapsedTime%1000)/10)
    const seconds = parseInt((elapsedTime/1000)%60)
    const minutes = parseInt((elapsedTime/(1000*60))%60)
    const hour = parseInt((elapsedTime/(1000*60*60))%24);
    displayTime(hour, minutes, seconds, milliseconds)
  }, 100);
}
Enter fullscreen mode Exit fullscreen mode

4. Creating a function to display our time to the DOM

Lastly, we need to display our time to the user. First, I am adding a leading zero if any time measurement is less than 10(optional). Then I am updating the text within our time HTML element.

function displayTime(hour, minutes, seconds, milliseconds) {
  const leadZeroTime = [hour, minutes, seconds, milliseconds].map(time => time < 10 ? `0${time}` : time)
  time.innerHTML = leadZeroTime.join(':')
}
Enter fullscreen mode Exit fullscreen mode

Final Code

const time = document.querySelector('.stopwatch')
const mainButton = document.querySelector('#main-button')
const clearButton = document.querySelector('#clear-button')
const stopwatch = { elapsedTime: 0 }

mainButton.addEventListener('click', () => {
  if (mainButton.innerHTML === 'Start') {
    startStopwatch();
    mainButton.innerHTML = 'Stop'
  } else {
    stopwatch.elapsedTime += Date.now() - stopwatch.startTime
    clearInterval(stopwatch.intervalId)
    mainButton.innerHTML = 'Start'
  }
})

clearButton.addEventListener('click', () => {
  stopwatch.elapsedTime = 0
  stopwatch.startTime = Date.now()
  displayTime(0, 0, 0, 0)
})

function startStopwatch() {
  //reset start time
  stopwatch.startTime = Date.now();
  //run `setInterval()` and save id
  stopwatch.intervalId = setInterval(() => {
    //calculate elapsed time
    const elapsedTime = Date.now() - stopwatch.startTime + stopwatch.elapsedTime
    //calculate different time measurements based on elapsed time
    const milliseconds = parseInt((elapsedTime%1000)/10)
    const seconds = parseInt((elapsedTime/1000)%60)
    const minutes = parseInt((elapsedTime/(1000*60))%60)
    const hour = parseInt((elapsedTime/(1000*60*60))%24);
    //display time
    displayTime(hour, minutes, seconds, milliseconds)
  }, 100);
}

function displayTime(hour, minutes, seconds, milliseconds) {
  const leadZeroTime = [hour, minutes, seconds, milliseconds].map(time => time < 10 ? `0${time}` : time)
  time.innerHTML = leadZeroTime.join(':')
}
Enter fullscreen mode Exit fullscreen mode

Though this is a pretty simple exercise, it's a great exercise for programmers new to javascript. We follow the Unobtrusive JavaScript principle by using event handlers. And most importantly, we went over some gotchas of working with javascript call stack and some workarounds.

Here is the repo for this exercise: https://github.com/chrislemus/stopwatch-using-javascript

Top comments (0)