Cover image for stopwatch using javascript - accurate and easy Chris Chris Posted on Apr 2, 2021 • Updated on Apr 26, 2021 3 2 stopwatch using javascript - accurate and easy # javascript # stopwatch 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) 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

00:00:00:00

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); } Writing our program We will break down our program into four steps Creating our variables Add event listener to our buttons Creating our stopwatch function 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 } 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' } }) 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) }) 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); } 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(':') } 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(':') } 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) Subscribe pic Add to the discussion Code of Conduct • Report abuse profile Platform.sh PROMOTED Deploy your node.js applications seamlessly Experience seamless app deployment, even on Fridays 😎 Deploy your apps seamlessly on an all-in-one PaaS. 🚀 Flexible, automated infrastructure provisioning. 🎯 Multicloud and multistack. 👾 Safe, secure and reliable around-the-clock. 👉 Get a 30-day free trial to build and deploy your way. Claim your free trial Read next oktimmy profile image How To Use Superstruct in an Express.js Application Timilehin Okunola - Apr 1 ninjin profile image Decomposition of Software Components Jin - Apr 1 mince profile image MASTER WEB DEV Let'sGoDev - Mar 31 sajidrsk profile image MistCSS : Create React components using CSS Only!! 🚀 Sajid Sheikh - Mar 31 Chris Follow LOCATION Raleigh, NC JOINED Dec 17, 2020 More from Chris Advice for Aspiring Software Engineers #softwareengineer #javascript #learning #juniorse Array methods and callback functions for beginners #javascript #arrays #callbacks #functional React Hook Form - Simple Todo List #javascript #forms #reacthookform #react profile Sentry PROMOTED Billboard image If seeing this in NextJS makes you 🤮, get Sentry. Try Sentry

00:00:00:00

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(':') }