Countdown timer
This is the 29th project of WesBos's JS30 series. To see the whole 30 part series, click here Today we'll build a custom JavaScript countdown timer from scratch.
Video -
Starter code -
All the HTML and CSS is already given to us,
<div class="timer">
<div class="timer__controls">
<button data-time="20" class="timer__button">20 Secs</button>
...
<form name="customForm" id="custom">
<input type="text" name="minutes" placeholder="Enter Minutes">
</form>
</div>
<div class="display">
<h1 class="display__time-left"></h1>
<p class="display__end-time"></p>
</div>
</div>
This is the HTML display, div.timer__controls
holds all the buttons and the form for custom entering the minutes. The data-time
attribute hold the time in seconds that the corresponding button sets.
h1.display__time-left
is the countdown display, p.display__end-time
displays the end time when the count down will run out.
We may be initially tempted to just use setInterval()
and update the countdown, it'll work in most cases, but it isn't reliable. If the browser is minimized or if there are other computation heavy function blocking the event loop, the interval callback may not be called to the second. Hence we take a different approach. We'll have the callback check the remaining time by comparing to the current time.
//handles to displays and controls
const timerDisplay = document.querySelector('.display__time-left');
const endTime = document.querySelector('.display__end-time');
const buttons = document.querySelectorAll('[data-time]');
let countdown; // this is the ret value if setInterval
function timer(seconds) {
// clear any existing timers
clearInterval(countdown);
const now = Date.now();
// convert seconds to mili seconds
const then = now + (seconds * 1000);
displayTimeLeft(seconds);
countdown = setInterval(() => {
const secondsLeft = Math.round((then - Date.now()) / 1000);
// check if we should stop the countdown
if(secondsLeft < 0) clearInterval(countdown);
// else display secondsLeft
else displayTimeLeft(secondsLeft);
}, 1000);
}
function displayTimeLeft(seconds) {
//display logic
}
Well we did quite a few things here, lets see what we have!
The timer(sec)
function sets a timer for sec
seconds. now
is the time (in mili seconds) the timer is being set, then
is the time when the timer will run out.
We have a setInterval
which is supposed to run every second, even if the callback is missed, no issues since we're calculating the seconds left using the final time then
and the time the callback runs. That's how we get secondsLeft = Math.round((then - Date.now()) / 1000)
We call displayTimeLeft
with the initial number of seconds in timer()
, then it is called in the setInterval
callback to display secondsLeft
.
Once the secondsLeft
gets lesser than zero, the interval is cleared.
Now for the seconds left display logic -
function displayTimeLeft(seconds) {
const minutes = Math.floor(seconds / 60);
const remainderSeconds = seconds % 60;
const display = `${minutes}:${remainderSeconds < 10 ? '0' : '' }${remainderSeconds}`;
document.title = display;
timerDisplay.textContent = display;
}
From the seconds, extract the whole minutes, then find the reminder seconds. In the display we check if remainderSeconds
is a single digit, if so we pad it with a extra '0'. We then update the h1.display__time-left
and the title of the page with display
. The title of the page is displayed in the browser tab area.
Now to display the end time (just under the count down timer display).
// in timer()
displayEndTime(then)
function displayEndTime(timestamp) {
const end = new Date(timestamp);
const hour = end.getHours();
const adjustedHour = hour > 12 ? hour - 12 : hour;
const minutes = end.getMinutes();
const adjustedMinutes = `${minutes < 10 ? '0' : ''}${minutes}`
endTime.textContent = `Be Back At ${adjustedHour}:${adjustedMinutes}`;
}
displayEndTime(endTime)
gets the end time in ms as a parameter. We extract the hour, convert it to 12 hour format. Get the minutes, again if it is in single digits, pad it with an extra zero. Display the result in p.display__end-time
Now to get the user input to set the timer.
For each of the buttons we get the necessary time from data-time
attribute and make a call to timer()
. This is very straightforward.
function startTimer() {
const seconds = parseInt(this.dataset.time);
timer(seconds);
}
buttons.forEach(button => button.addEventListener('click', startTimer));
The form component is a bit more involved. We listen to when the user 'submits' the form, i.e. type the number and press enter.
document.customForm.addEventListener('submit', function(e) {
e.preventDefault();
const mins = this.minutes.value;
timer(mins * 60);
this.reset();
});
We prevent the default action, get the value (represents minutes) from the text input and then call the timer. We then reset the form to empty the text field.
This completes our count down timer project, here is the final code -