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
<!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>
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)