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