How to build a stopwatch with HTML, CSS, and plain JavaScript (Part 2)
Published: 2020-07-09 17:26:42

---
series: How to build a stopwatch with HTML, CSS, and plain JavaScript
canonical_url: https://tinloof.com/blog/how-to-build-a-stopwatch-with-html-css-js-react-part-2/
---

<figure>
  <img loading="lazy" src="https://res.cloudinary.com/https-tinloof-com/image/upload/v1591011589/blog/time-in-js/Functional.gif" alt="Stopwatch built with HTML, CSS, and JS" />
  <figcaption>Build your own stopwatch with HTML, CSS, and JavaScript</figcaption>
</figure>

This series of articles is made out of two parts:

[In the first part](https://tinloof.com/blog/how-to-build-a-stopwatch-with-html-css-js-react-part-1/), we built the stopwatch's user interface with HTML and CSS.

In this second part, we'll make the user interface functional with JavaScript (the stopwatch works).

### Final result

This is what we will build:

Here's the [link to the GitHub repo](https://github.com/tinloof/gold-stopwatch).

Here's the link to the [stopwatch itself.](https://gold-stopwatch.netlify.app/)

If you want to practice with JavaScript, you can download [RunJS](https://runjs.dev/), a JS notepad that serves as an alternative to the browser's console.

# Time in JavaScript

Before we get started with the stopwatch's functionality, let's take some time to understand two fundamental concepts about time in JavaScript: 

1. Basics of the `Date` object
2. How to format time

## Basics of the `Date` object

Let's get familiar with some concepts.

In JavaScript, the global object `Date` is used to manage date and time.

We can use the `Date()` object to get the moment elapsed since January 1, 1970, 00:00:00 UTC, which is also known as the Unix time or Epoch time. 

There is a built-in function in JavaScript called `Date.now()` that returns the number of milliseconds elapsed since then:

```js
// output: 1590946806933
Date.now();
```

The above means that the number of milliseconds elapsed between Epoch time and the moment I wrote `Date.now()` is 1590946806933.

I type `Date.now()`  a second time and get the following timestamp:

```js
// output: 1590946936532
Date.now();
```

We get a larger number this time because more time elapsed compared to the first time I wrote `Date.now()`. 

If I keep refreshing, the timestamp will just get bigger. Every second it will grow by 1000.

This means that between the first time I wrote `Date.now()`  and the second time I wrote `Date.now()`, the following milliseconds elapsed:

```js
1590946806933 - 1590946936532 = 129599 milliseconds
```

**This is the foundation of the stopwatch we will build.** 

The moment we click on the Play button, we will store a timestamp in a variable, and another function will constantly run to calculate the difference between `Date.now()` and that variable we created.

If you still don't understand exactly how the stopwatch will work, don't worry, there will be plenty of explanation later.

## How to format time in JavaScript

### Format time from milliseconds to other units (hours, minutes, seconds)

Let's say we already obtained a time difference in milliseconds between two dates:

```js
// (time in ms)
let time = 10000000; 
```

We want to convert that number to a formatted string with the format *hh:mm:ss* (*hh* stands for hours, *mm* for minutes, and *ss* for seconds).

How to do that?

We know that 1 hour is equal to 3600000 ms (1000 * 60 * 60).

So dividing 'time' by 3600000 should give us the number of hours in it:

```js
// output: 2.7777777777777777
let diffInHrs = time / 3600000;
```

`diffInHrs` is 2 hours and 0.7777777777777777 hours. 

This will not fit in our `hh:mm:ss` format.

To get `hh`, we get the integer value of `diffInHrs` using `Math.floor` (which returns the largest integer smaller than 2.7777, i.e. 2):

```js
// output: 2.7777777777777777
let diffInHrs = time / 3600000;

// output: 2
let hh = Math.floor(diffInHrs);
```

We know that 1 hour is 60 minutes, so let's convert the remaining decimal part of `diffInHrs`:

```js
// output: 46.6666666
let diffInMin = (diffInHrs - hh) * 60;
```

`diffInMin` is 46 min and 0.666666 min. 

To get `mm` and `ss` we follow the same logic for `hh`  keeping in mind that 1 minute is 60 seconds:

```js
// output: 2.7777777777777777
let diffInHrs = time / 3600000;

// output: 2
let hh = Math.floor(diffInHrs);

// output: 46.6666666
let diffInMin = (diffInHrs - hh) * 60;

// output: 46
let mm = Math.floor(diffInMin);

// output: 39.9999999943
let diffInSec = (diffInMin - mm) * 60;

// output: 39
let ss = Math.floor(diffInSec);
```

Now we can easily use [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) to output our time:

```js
// output: 2:46:39
console.log(`${hh}:${mm}:${ss}`);
```

We want to be able to reuse that later, so let's write a function `timeToString` that converts a given time from ms to a formatted string (hh:mm:ss):

```js
function timeToString(time) {
  let diffInHrs = time / 3600000;
  let hh = Math.floor(diffInHrs);

  let diffInMin = (diffInHrs - hh) * 60;
  let mm = Math.floor(diffInMin);

  let diffInSec = (diffInMin - mm) * 60;
  let ss = Math.floor(diffInSec);

  return `${hh}:${mm}:${ss}`;
}
```

### Format time to double-digits format

The output we get ("2:46:39") is correct, but we can already predict an obstacle.

By looking at the result, we can see that when we get a single digit (e.g. 2 hours), it shows "2" and not "02". 

Our stopwatch is designed to take double-digits with the following format "00:00:00".

Let's use padStart() to have double-digits instead of single-digits:

```js
function timeToString(time) {
  let diffInHrs = time / 3600000;
  let hh = Math.floor(diffInHrs);

  let diffInMin = (diffInHrs - hh) * 60;
  let mm = Math.floor(diffInMin);

  let diffInSec = (diffInMin - mm) * 60;
  let ss = Math.floor(diffInSec);

  let formattedHH = hh.toString().padStart(2, "0");
  let formattedMM = mm.toString().padStart(2, "0");
  let formattedSS = ss.toString().padStart(2, "0");

  return `${formattedHH}:${formattedMM}:${formattedSS}`;
}

// output: "02:46:39"
```

You can read more about [padStart()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart).

We now know how to get a time unit in JavaScript and format it to different units. 


# Make the stopwatch functional

### Add a `click` event listener

> **Note:**  a `click` event listener is a function called whenever the user clicks on the element attached to it.

We want to make the stopwatch functional by making the Play, Pause, and Reset buttons work. 

We will use `addEventListener` to call the relevant function whenever we click on a button:

```js
document.getElementById("playButton").addEventListener("click", start);
document.getElementById("pauseButton").addEventListener("click", pause);
document.getElementById("resetButton").addEventListener("click", reset);
```

To avoid calling `document.getElementById` multiple times in the future, we can store the button elements in variables: 

```js
let playButton = document.getElementById("playButton");
let pauseButton = document.getElementById("pauseButton");
let resetButton = document.getElementById("resetButton");

playButton.addEventListener("click", start);
pauseButton.addEventListener("click", pause);
resetButton.addEventListener("click", reset);
```

Note that we haven't created the "start", "pause", and "reset" functions yet, clicking on the buttons won't result in anything.

### Create `setInterval`

The stopwatch will capture the time elapsed between `Date.now()` stored in a variable the moment we click on the button and a new `Date.now()` that will be updated every second.

When we click on the Play button, we'll create a variable (let's name it *startTime*) that will store the timestamp.

What we want to display instead of "00:00:00" is the formatted time difference between an automatically refreshing `Date.now()` and *startTime*, which stores a timestamp*.*

Let's imagine *startTime* is 0 and `Date.now()` is 0 too, then it will display "00:00:00". 

After 1000 milliseconds (1 second), *startTime* will remain as "0", but the new `Date.now()` will output "1000", therefore the difference of 1000 milliseconds will output "00:00:01" on the page. 

After 2000 milliseconds (2 seconds), *startTime* will remain as "0", but the new `Date.now()` will output "2000", therefore the difference of 2000 milliseconds will output "00:00:02" on the page.

And so on.

To constantly update `Date.now()`, we use a JavaScript method called [setInterval](https://www.w3schools.com/jsref/met_win_setinterval.asp). 

This method calls a function at specified time intervals.

To calculate the time difference every second, we do the following:

```js
setInterval(function printTime() {
  let elapsedTime = Date.now() - startTime;
}, 1000);
```

Every 1000 milliseconds, a function will call *elapsedTime,* which stores `Date.now()`, that is constantly changing.

We are now ready to create the *start*, *pause* and *reset* functions.

### Create the *start* function

Since we'll display the result in the `div` containing "00:00:00", let's create an `id` and call it *display*:

```html
<body>
    <div class="stopwatch">
      <h1><span class="gold">GOLD</span> STOPWATCH</h1>
      <div class="circle">
        <span class="time" id="display">00:00:00</span>
      </div>
```

Let's create a function so when we click on the Play button, we start counting the time:

```js
let startTime;
let elapsedTime;

function start(){
    startTime = Date.now();
    setInterval(function printTime(){
        elapsedTime = Date.now() - startTime;
        document.getElementById("display").innerHTML = timeToString(elapsedTime);
    }, 1000)
}
```

We first declare two variables: *startTime* and *elapsedTime*.

We then create a function and store `Date.now()` in the *startTime* variable as we discussed earlier.

Then, using `setInterval`, we display *elapsedTime*, which is the difference between `Date.now()` refreshed every 1000 milliseconds and the variable *startTime*.

To change the HTML on the page, we use the `innerHTML` property.

Excellent! The stopwatch works, but we are not done yet.

The time moves slowly, so instead of displaying hours, minutes, and seconds, we'll display minutes, seconds, and milliseconds:

```js
function timeToString(time) {
  let diffInHrs = time / 3600000;
  let hh = Math.floor(diffInHrs);

  let diffInMin = (diffInHrs - hh) * 60;
  let mm = Math.floor(diffInMin);

  let diffInSec = (diffInMin - mm) * 60;
  let ss = Math.floor(diffInSec);

  let diffInMs = (diffInSec - ss) * 1000;
  let ms = Math.floor(diffInMs);

  let formattedMM = mm.toString().padStart(2, "0");
  let formattedSS = ss.toString().padStart(2, "0");
  let formattedMS = ms.toString().padStart(2, "0");

  return `${formattedMM}:${formattedSS}${formattedMS}`;
}
```

`diffInMs` will always be anywhere between 0 and 999. If we want to display them in 2 digits instead of 3, we can divide it by 10 (or just multiply by 100 instead of 1000):

```js
function timeToString(time) {
  let diffInHrs = time / 3600000;
  let hh = Math.floor(diffInHrs);

  let diffInMin = (diffInHrs - hh) * 60;
  let mm = Math.floor(diffInMin);

  let diffInSec = (diffInMin - mm) * 60;
  let ss = Math.floor(diffInSec);

  let diffInMs = (diffInSec - ss) * 100;
  let ms = Math.floor(diffInMs);

  let formattedMM = mm.toString().padStart(2, "0");
  let formattedSS = ss.toString().padStart(2, "0");
  let formattedMS = ms.toString().padStart(2, "0");


  return `${formattedMM}:${formattedSS}:${formattedMS}`;
}
```

And we change the `setInterval` delay argument from 1000 milliseconds to 10 milliseconds so it refreshes more often:

```js
function start(){
    startTime = Date.now();
    setInterval(function printTime(){
        elapsedTime = Date.now() - startTime;
        document.getElementById("display").innerHTML = timeToString(elapsedTime);
    }, 10)
}
```

The stopwatch works well. When we click on the Play button, the stopwatch starts counting elapsed minutes, seconds, and milliseconds.

Let's separate our printing logic into a separate *print* function to improve readability:

```js
function print(txt) {
  document.getElementById("display").innerHTML = txt;
}

function start() {
  startTime = Date.now();
  setInterval(function printTime() {
    elapsedTime = Date.now() - startTime;
    print(timeToString(elapsedTime));
  }, 10);
}
```

Next, we want to make the Pause button appear the moment we click on the Play button. Let's modify our *start* function:

```js
function start() {
  startTime = Date.now();
  setInterval(function printTime() {
    elapsedTime = Date.now() - startTime;
    print(timeToString(elapsedTime));
  }, 10);
  playButton.style.display = "none";
  pauseButton.style.display = "block";
}
```

Excellent, it works. When we click on the Play button, it switches to the Pause button. 

Let's separate this logic into a showButton function that we can reuse later:

```js
function showButton(buttonKey) {
  const buttonToShow = buttonKey === "PLAY" ? playButton : pauseButton;
  const buttonToHide = buttonKey === "PLAY" ? pauseButton : playButton;
  buttonToShow.style.display = "block";
  buttonToHide.style.display = "none";
}
```

If we call this function with `showButton("PLAY")`, it will display the Play button and hide the Pause button. 

If we call this function with `showButton("PAUSE")`, it will display the Pause button and hide the Play button.

Let's call it in our *start* function:

```js
function start() {
  startTime = Date.now();
  setInterval(function printTime() {
    elapsedTime = Date.now() - startTime;
    print(timeToString(elapsedTime));
  }, 10);
  showButton("PAUSE");
}
```

### Create the *pause* function

Now that our *start* function works, let's create the *pause* function.

The reason the stopwatch is constantly active is because of the `setInterval` in the *start* function. The `Date.now()` keeps getting refreshed and its output grows, and so does *elapsedTime*.

To pause the stopwatch, we need a way to stop `setInterval`. 

The global method `clearInterval` does just that.

To call `clearInterval`, we need to modify the *start* function and store `setInterval` in a variable:

```js
let startTime;
let elapsedTime;
let timerInterval;

function start(){
    startTime = Date.now();
    timerInterval = setInterval(function printTime(){
        elapsedTime = Date.now() - startTime;
        print(timeToString(elapsedTime));
    }, 10)
}
```

Now that we create the variable *timerInterval*, we can create a *pause* function:

```js
function pause (){
    clearInterval(timerInterval);
}
```

It works! However, when we click on the Pause button, we want to display the Play button so we can activate the stopwatch again. 

Let's modify our *pause* function:

```js
function pause (){
    clearInterval(timerInterval);
    showButton("PLAY");
}
```

When we click on the Pause button, the Pause button disappears and the Play button appears. 

However, when click on Play after we clicked on Pause for the first time, the stopwatch starts from zero instead of starting from the recorded time where we left it at.

To understand this, let's illustrate in a simpler version of reality where `Date.now()` is initially 0:

```js
// start, startTime = Date.now() = 0

// Date.now() = 0
// elapsedTime = Date.now() - startTime = 0
print("00:00:00")

// Date.now() = 1
// elapsedTime = Date.now() - startTime = 1
print("00:01:00")

// Date.now() = 2
// elapsedTime = Date.now() - startTime = 2
print("00:02:00")

// pause
// wait 3 seconds

// start, startTime = Date.now() = 5

// Date.now() = 5
// elapsedTime = Date.now() - startTime = 0
print("00:00:00")

// Date.now() = 6
// elapsedTime = Date.now() - startTime = 1
print("00:01:00")
```

The problem is that when we run our start function after pause, it sets the startTime to that time (i.e. 5).

This is not accurate because our *startTime* is supposed to where we left it at, in this case, 2. To make that happen, we can just delete the elapsed time from it and make *elapsedTime* start from 0:

```js
let startTime;
let elapsedTime = 0;
let timerInterval;

function start() {
  startTime = Date.now() - elapsedTime;
  timerInterval = setInterval(function printTime() {
    elapsedTime = Date.now() - startTime;
    print(timeToString(elapsedTime));
  }, 10);
  showButton("PAUSE");
}
```

The stopwatch will no longer start from zero when we click on Play after clicking on Pause.

### Create the *reset* function

Great, both our *start* and *pause* functions work, now let's create a *reset* function.

Similarly to the *pause* function, *reset* will `clearInterval`, however, it will replace any captured time with "00:00:00", and regardless of the displayed button (Play or Pause), it will display the Play button.

It will also set back *elapsedTime* back to "0".

In the previous part of this series, we gave the Reset button the `id` *resetButton*.

Now let's create the *reset* function:

```js
function reset() {
  clearInterval(timerInterval);
  print("00:00:00");
  elapsedTime = 0;
  showButton("PLAY");
}
```

Our stopwatch works perfectly fine!