Creating a Stopwatch in React.js and CSS
Tags: react, css, stopwatch, microproject
Published: 2021-07-03 02:41:15

---
title: Creating a Stopwatch in React.js and CSS
published: true
description: 
tags: react, css, stopwatch, microproject
cover_image: https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y3arg0v9io7y5p86uyk6.png
---
Once upon a time I was interviewing for a Principal Front-end Engineer role and was challenged to create an analog clock in vanilla JS. In real time. I was given a little less than an hour to complete the exercise, but crushed it in under 15 minutes. I had never coded a clock before, and was shocked how easy it was. Recently, I found myself wanting to continue that exercise, but with something more complex, and maybe even interactive.

I decided a stopwatch would be the perfect [micro-project](https://dev.to/horaceshmorace/the-absolute-one-best-secret-to-becoming-an-awesome-software-developer-1ip1). The math was basic, but there were some concepts that were new and maybe even tricky.

###TLDR: I made a stopwatch in React and CSS and it's pretty sweet. Check it out:
{% codepen https://codepen.io/HoraceShmorace/pen/LYWKexo %}

If you're still here, let's walk through the project.

## Functional Requirements
The first thing you need to think about when starting on any application is: What is this thing even going to do? What are its functions? After refreshing my knowledge of stopwatch functionality by looking at several online (because what normal person is just using stopwatches regularly?), I decided that my basic requirements should be:

1. **Start** a timer by tracking the number of milliseconds elapsed between the start time and "now."
1. **Mark** any number of laps, which simply involves saving a list of arbitrary timestamps.
1. **Stop** (pause) the timer.
1. **Resume** the stopped timer.
1. **Reset** the timer to 0.

With those requirements in mind, this is what our API looks like:
```javascript
const Stopwatch = () => {
  const start = () => {} // also restarts
  const mark = () => {}
  const stop = () => {}
  const reset = () => {}
}
```

## User Interface
The next thing you need to think about is UI functionality. What will the user see? How will she interact with the application? From our functional requirements, I knew I'd need:
    
1. **Elapsed time** readout.
1. **List of marked laps.**
1. **Controls** to start, stop, resume, and reset the timer, and to mark laps.

From these visual requirements we can infer the basic components we'll need.

###A watch face
For pure visual coolness, I want to show the elapsed time as an analog stopwatch with minute, second, and millisecond hands spinning around the center.

1. **Watch hands**, one each to indicate minutes, seconds, and milliseconds. These were abstracted into a general `Hand` component. Each hand will have some common styling, but will be differentiated by color. At any given time, each hand will be rotated according to its value, which we'll achieve by an inline style that applies a transform rule with `translateX(-50%)`, and `rotate` set to the applicable value via a `value` prop.

1. **Tick marks**: A ring of light gray tick marks are spaced for each second (1/60), darker and thicker tick marks at 5-second intervals, and darker and even thicker tick marks at 15-second intervals. I used zero HTML/JSX elements to make these. They are created using a conical gradient in CSS applied to the `::before` pseudo element of the watch face. This was admittedly a little tricky to figure out at first, but the math was ultimately embarassingly simple:
    * 1-second intervals: 360 degrees in a circle divided by 60 seconds in a minute = a tick mark every 1.67 degrees
    * 5-second intervals: 360/12 = every 30 degrees
    * 15-second intervals: 360/4 = every 90 degrees

Thus, my 3 repeating conical gradients in CSS:

    background-image: repeating-conic-gradient(
      from 359deg,
      #555 0 2deg, transparent 2deg 90deg      
    ), repeating-conic-gradient(
      from 359.5deg,
      #555 0 1deg, transparent 1deg 30deg      
    ), repeating-conic-gradient(
      from 359.75deg,
      #ccc 0 .5deg, transparent .5deg 6deg      
    );

This creates something like this:
![Conical gradients](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bqgkxbg2qrzj7zlol2bd.png)

Then I'd need a mask to turn these gradients into tick marks by obscuring (or *masking*) all but the ends of them:

    mask-image: radial-gradient(
      circle at center,
      transparent 66%,
      #fff 66.1%
    );

Which results in:
![Completed tick marks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uk0vne4ej5q8vc7jmlia.png)

###Controls
I'd need a button bar to show our controls

1. **Start button** to start the timer. This button serves double duty as the "Lap" button while the timer is running.
1. **Stop button** that pauses the timer.
1. **Reset button** that completely resets the Stopwatch component to its original "zeroed" state. 

###Digital Readout
In addition to the analog clock to also show elapsed time, I decided to add a digital readout (in MM:SS:ss format), because it's more readable. Oddly, this is the meatiest part of our code: converting our elapsed time in milliseconds to the whole minutes, whole seconds, and remaining milliseconds.

I would need to get *only* the whole minutes and seconds, no remainders and nothing less than 0. I ensure the former by applying `Math.floor(value)` to always round down to the nearest whole number, and the latter by applying `Math.max(0, value)` to replace any value less than zero with zero. I saved this as a convenience function, and define some useful constants:

```
const getNumOrZero = num => Math.floor(Math.max(0, num))
const ONE_SECOND_MS = 1000
const ONE_MINUTE_MS = ONE_SECOND_MS * 60
``` 

####Whole minutes
Now to get the whole minutes value, I could simply divide the total `elapsed` milliseconds by the number of milliseconds in a minute (`ONE_MINUTE_MS`), rounding down to get the whole minutes without the remainder (`Math.floor` via `getNumOrZero()`):

``` 
const wholeMinutesValue = getNumOrZero(elapsed / ONE_MINUTE_MS)
```

I'll need this value back in milliseconds later, so I can simply multiply it by `ONE_MINUTE_MS`:

```
const wholeMinutesInMs = wholeMinutesValue * ONE_MINUTE_MS
```

####Whole seconds
I then do the same thing to get the whole seconds. I divide the total `elapsed` milliseconds, *minus the `wholeMinutesInMs` calculated above*, by `ONE_SECOND_MS` (milliseconds in a second). This gives me the number of whole seconds remaining *after* subtracting the whole minutes:

```
const wholeSecondsValue = getNumOrZero((elapsed - wholeMinutesInMs) / ONE_SECOND_MS)
const wholeSecondsInMs = wholeSecondsValue * ONE_SECOND_MS
 ```
####Remaining milliseconds 
I can easily get the remaining milliseconds after subtracting the `wholeMinutesInMs` and `wholeSecondsInMs` from the total elapsed time in milliseconds:

```
const millisecsValue = elapsed - wholeMinutesInMs - wholeSecondsInMs
```
####Assembling the digital elapsed time readout
Now I could easily assemble my digital readout, being sure to left pad the minutes and seconds values with a zero for values < 10:

```
 const elapsedFormatted = `${wholeMinutesValue.toString().padStart(2, '0')}:` +
  `${wholeSecondsValue.toString().padStart(2, '0')}:` +
  `${millisecsValue.toString().padStart(3, '0')}`
```
And I can render this:

![Digital readout](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4j1tgkoyn6up3m0lmh6.png)
 
 
###Marked Laps
The last UI component is a list of marked laps. I used an ordered list, but in reverse order so that the most recent lap is at the top of the list.

```
<ol className="time lap" reversed>{ lapList }</ol>
```

`lapList` is an array of lap timestamps in the same MM:SS:ss format as the digital readout. Note the `reversed` HTML attribute, which (as you might suspect) reverses the order of an ordered list.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/01mj4spoinq2q4moujsx.png)
 
## The finished project
What I ended up with is a simple, slick, functional stopwatch: ![React/SCSS Stopwatch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5qk9lid33jntsbjkr9n1.png)

And just for fun, I added a dark mode by abstracting the colors into SCSS variables and toggling a class:

![React/SCSS Stopwatch in Dark Mode](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cc6y6zr1ejjtmh4ey0r5.png)

I'm pretty pleased with how it turned out. The code is totally straightforward, but if you have any questions just drop them in a comment below!