Digital Stopwatch & Lap Timer
Create a digital stopwatch application with precision time tracking, start, pause, reset buttons, and lap recording functionality.
Overview & What You Will Learn
What We Are Building
In this project, we are building a high-precision digital stopwatch application with lap timer split functionality. The app allows users to start, pause, resume, and reset the timer, as well as record lap times which are displayed dynamically in a scrollable split history list.
How We Are Building It
We structure the app using an HTML container with a digital LCD display screen, control buttons, and an un-ordered list for lap splits. We style the display with high-contrast dark theme colors and monospace typography. Using JavaScript, we measure elapsed time accurately with Date.now() timestamps, run a 10ms timer interval loop, format time units (minutes, seconds, hundredths of a second), and prepend new lap split records onto the DOM dynamically.
Key Skills & Concepts You Will Learn:
- Accurate time tracking using JavaScript Date.now() timestamp arithmetic
- Managing interval loops with setInterval() and clearInterval()
- Formatting raw milliseconds into human-readable time strings (00:00.00)
- Dynamic DOM element creation and insertion using insertBefore()
- Toggling element states (disabled/enabled, Start/Pause) based on application status
Interactive Live Demo
Step-by-Step Tutorial
We construct a centered stopwatch container using <div> elements. Inside, we place a title, a primary timer display screen for minutes, seconds, and hundredths of a second, followed by three control keys using <button> tags (Start/Pause, Lap, and Reset).
<div class="stopwatch-container">
<div class="stopwatch-title">Digital Stopwatch</div>
<div class="timer-display" id="display">
00:00<span class="milliseconds">.00</span>
</div>
<div class="controls">
<button id="start-btn" class="btn btn-start">Start</button>
<button id="lap-btn" class="btn btn-lap" disabled>Lap</button>
<button id="reset-btn" class="btn btn-reset">Reset</button>
</div>
</div>disabledattribute on the Lap button prevents recording split times before the timer has started.
Below the control buttons, we add an unordered list (<ul>) to serve as a container for dynamically generated split lap records.
<ul id="laps-list" class="laps-list">
<!-- Lap items dynamically inserted via JS -->
</ul>- Use semantic
<ul>elements for accessible list structures.
We style the stopwatch display to resemble a dark digital LCD screen using dark background colors, high-contrast neon text, and a monospace font stack using CSS font-family. Controls use CSS Flexbox Layout for centered alignment.
.timer-display {
font-family: monospace;
background-color: #1e1e2e;
color: #50fa7b;
font-size: 2.2rem;
padding: 20px;
border-radius: 12px;
text-align: center;
margin-bottom: 20px;
}
.controls {
display: flex;
gap: 12px;
justify-content: center;
}- Using a monospace font stack ensures numbers maintain identical character widths as digits update rapidly.
We use Date.now() timestamps to measure elapsed time accurately instead of relying solely on tick counts. Clicking Start launches a setInterval() timer loop that recalculates elapsed time every 10 milliseconds and formats time units onto the screen using DOM manipulation.
function startTimer() {
if (!isRunning) {
isRunning = true;
startTime = Date.now() - elapsedTime;
timerInterval = setInterval(() => {
elapsedTime = Date.now() - startTime;
display.innerHTML = formatTime(elapsedTime);
}, 10);
}
}- Calculating time using
Date.now() - startTimeguarantees precise elapsed time even when browser tabs are backgrounded. - Always clear active timer handles with
clearInterval()when pausing or resetting.
Each time the user clicks "Lap", we subtract the previous total elapsed time from current elapsed time to compute the split lap duration, dynamically create a new <li> element, and insert it at the top of the list using JavaScript.
function recordLap() {
const currentLapTime = elapsedTime - lastLapTime;
lastLapTime = elapsedTime;
const li = document.createElement('li');
li.innerHTML = `<span>Lap #${lapCounter}</span><span>${formatTime(currentLapTime)}</span>`;
lapsList.insertBefore(li, lapsList.firstChild);
}- Inserting laps using
insertBefore(li, lapsList.firstChild)keeps the newest split time right at the top for instant visibility.
Complete Project Source Code
<div class="stopwatch-container">
<div class="stopwatch-title">Digital Stopwatch</div>
<div class="timer-display" id="display">
00:00<span class="milliseconds">.00</span>
</div>
<div class="controls">
<button id="start-btn" class="btn btn-start">Start</button>
<button id="lap-btn" class="btn btn-lap" disabled>Lap</button>
<button id="reset-btn" class="btn btn-reset">Reset</button>
</div>
<div class="laps-section">
<div class="laps-header">
<span>Lap #</span>
<span>Lap Time</span>
<span>Total Time</span>
</div>
<ul id="laps-list" class="laps-list">
<li class="empty-laps">No laps recorded yet.</li>
</ul>
</div>
</div>