How to Create an Animated Countdown Timer With HTML, CSS and JavaScript for Blog.
Have you ever needed a countdown timer on a blog?For something like this & it might be natural to do for a plugins, but it’s actually a lot more straight-forward to make one than you might think & only requires the trifecta of HTML, CSS & JavaScript. Let’s make one together !!!
This is what we’re aiming for :::
Here are a few things the timer does that we’ll be covering in this post:::
- Displays the initial time remaining
- Converts the time value to a MM:SS format
- Calculates the difference between the initial time is remaining and how much time has passed away.
- Changes color as the time remaining nears zero
- Displays the progress of time remaining as an animated ring bar.
OK, that’s what we want, so let’s make it happen !!!
1. Start with the normal markup and styles
Let’s start with creating a normal template for our timer. We are adding an svg with a circle element inside to draw a timer ring that's indicate the passing time & add a span to show the remaining time value. Note that we are writing the HTML in JavaScript & injecting into the DOM by targeting the #app element. Sure, we could move a lot of it into an HTML file, if that's more your thing.
document.getElementById("app").innerHTML = `
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45" />
</g>
</svg>
<span>
<!-- Remaining time label -->
</span>
</div>
`;
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45" />
</g>
</svg>
<span>
<!-- Remaining time label -->
</span>
</div>
`;
Now that we have some markup to work with, let’s style it up a bit therefore we've a good View to start with. Specifically, we’re going to:::
- Set the timer’s size
- Remove the fill & stroke from the circle wrapper element so we get the round shape, but let the elapsed time show through
- Set the ring’s width and color.
/* Sets the containers height and width */
.base-timer {
position: relative;
height: 300px;
width: 300px;
}
/* Removes SVG styling that would hide the time label */
.base-timer__circle {
fill: none;
stroke: none;
}
/* The SVG path that displays the timer's progress */
.base-timer__path-elapsed {
stroke-width: 7px;
stroke: grey;
}
.base-timer {
position: relative;
height: 300px;
width: 300px;
}
/* Removes SVG styling that would hide the time label */
.base-timer__circle {
fill: none;
stroke: none;
}
/* The SVG path that displays the timer's progress */
.base-timer__path-elapsed {
stroke-width: 7px;
stroke: grey;
}
Having that done we end up with a normal template that looks like this :::
2. Setting up the time label
As you probably notice, the template includes an empty <span> that’s going to hold the time lefts. We're fill that place with a proper value. We're said earlier that the time will be in MM:SS format. To do that we're create a method called formatTimeLeft.
function formatTimeLeft(time) {
// The largest round integer less than or equal to the result of time divided being by 60.
const minutes = Math.floor(time / 60);
// Seconds are the remainder of the time divided by 60 (modulus operator)
let seconds = time % 60;
// If the value of seconds is less than 10, then display seconds with a leading zero
if (seconds < 10) {
seconds = `0${seconds}`;
}
// The output in MM:SS format
return `${minutes}:${seconds}`;
}
// The largest round integer less than or equal to the result of time divided being by 60.
const minutes = Math.floor(time / 60);
// Seconds are the remainder of the time divided by 60 (modulus operator)
let seconds = time % 60;
// If the value of seconds is less than 10, then display seconds with a leading zero
if (seconds < 10) {
seconds = `0${seconds}`;
}
// The output in MM:SS format
return `${minutes}:${seconds}`;
}
Then we will use our method in the template :::
document.getElementById("app").innerHTML = `
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
</g>
</svg>
<span id="base-timer-label" class="base-timer__label">
${formatTime(timeLeft)}
</span>
</div>
`
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
</g>
</svg>
<span id="base-timer-label" class="base-timer__label">
${formatTime(timeLeft)}
</span>
</div>
`
To show the value inside the ring's we need to update our styles a bit.
.base-timer__label {
position: absolute;
/* Size should match the parent container */
width: 300px;
height: 300px;
/* Keep the label aligned to the top */
top: 0;
/* Create a flexible box that centers content vertically and horizontally */
display: flex;
align-items: center;
justify-content: center;
/* Sort of an arbitrary number; adjust to your liking */
font-size: 48px;
}
position: absolute;
/* Size should match the parent container */
width: 300px;
height: 300px;
/* Keep the label aligned to the top */
top: 0;
/* Create a flexible box that centers content vertically and horizontally */
display: flex;
align-items: center;
justify-content: center;
/* Sort of an arbitrary number; adjust to your liking */
font-size: 48px;
}
OK, we're ready to do with the timeLeft value, but the value doesn’t exist yet. Let’s create it and set the initial value to our time limit.
// Start with an initial value of 20 seconds
const TIME_LIMIT = 20;
// Initially, no time has passed, but this will count up
// and subtract from the TIME_LIMIT
let timePassed = 0;
let timeLeft = TIME_LIMIT;
const TIME_LIMIT = 20;
// Initially, no time has passed, but this will count up
// and subtract from the TIME_LIMIT
let timePassed = 0;
let timeLeft = TIME_LIMIT;
And we are one step closer.
Right on !!! Now we've a timer that starts at 20 seconds… but it doesn't do any counting just yet. Let’s bring it to life so it counts down to zero seconds.
3. Counting down
Let’s think about what we need to count down the time. Right now !!! we have the timelimit value that signify our initial time & the time-Passed value that indicates how much time has passed once the countdown start.What we need to do is increase the value of time-Passed by one unit per second and recompute the time-Left value based on the new time-Passed value. We can achieve that using the set-Interval function.
Let’s implement a method called start-Timer that will:
- Set counter interval
- Increment the time-Passed value each second
- Recompute the new value of time-Left
- Update the label value in the template
We also need to keep the reference to that interval object to clear it when needed — that’s why we will create a timer-Interval variable.
let timerInterval = null;
document.getElementById("app").innerHTML = `...`
function startTimer() {
timerInterval = setInterval(() => {
// The amount of time passed increments by one
timePassed = timePassed += 1;
timeLeft = TIME_LIMIT - timePassed;
// The time left label is updated
document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
}, 1000);
}
document.getElementById("app").innerHTML = `...`
function startTimer() {
timerInterval = setInterval(() => {
// The amount of time passed increments by one
timePassed = timePassed += 1;
timeLeft = TIME_LIMIT - timePassed;
// The time left label is updated
document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
}, 1000);
}
We've a method that start the timer but we do not call it anywhere. Let’s start our timer immediately on load.
document.getElementById("app").innerHTML = `...`
startTimer();
startTimer();
That’s it !!! Our timer will now count down the time. While that’s great & all, it would be nicer if we could add some color to the ring around the time label & change the color at different time values.
4. Cover the timer ring with another ring
To visualise time passing, we necessary to add a second layer to our ring that handles the animation. Now we're doing is necessary frame up a green ring on top of the initial gray ring so that the green ring animates to reveal the gray ring as time passes, like a progress bar.Let’s Now add a path element in SVG elements.
document.getElementById("app").innerHTML = `
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
<path
id="base-timer-path-remaining"
stroke-dasharray="283"
class="base-timer__path-remaining ${remainingPathColor}"
d="
M 50, 50
m -45, 0
a 45,45 0 1,0 90,0
a 45,45 0 1,0 -90,0
"
></path>
</g>
</svg>
<span id="base-timer-label" class="base-timer__label">
${formatTime(timeLeft)}
</span>
</div>
`;
<div class="base-timer">
<svg class="base-timer__svg" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<g class="base-timer__circle">
<circle class="base-timer__path-elapsed" cx="50" cy="50" r="45"></circle>
<path
id="base-timer-path-remaining"
stroke-dasharray="283"
class="base-timer__path-remaining ${remainingPathColor}"
d="
M 50, 50
m -45, 0
a 45,45 0 1,0 90,0
a 45,45 0 1,0 -90,0
"
></path>
</g>
</svg>
<span id="base-timer-label" class="base-timer__label">
${formatTime(timeLeft)}
</span>
</div>
`;
Future, Now create an initial color for the remaining time path.
const COLOR_CODES = {
info: {
color: "green"
}
};
let remainingPathColor = COLOR_CODES.info.color;
Last, Now add few styles to make the circular path look like our initial gray ring. The crucial thing here is to make sure the stroke-width is the equal size as the initial ring & that the duration of the transition is set to one second so that it animates smoothly and corresponds with the time remaining in the time label.info: {
color: "green"
}
};
let remainingPathColor = COLOR_CODES.info.color;
.base-timer__path-remaining {
/* Just as thick as the original ring */
stroke-width: 7px;
/* Rounds the line endings to create a seamless circle */
stroke-linecap: round;
/* Makes sure the animation starts at the top of the circle */
transform: rotate(90deg);
transform-origin: center;
/* One second aligns with the speed of the countdown timer */
transition: 1s linear all;
/* Allows the ring to change color when the color value updates */
stroke: currentColor;
}
.base-timer__svg {
/* Flips the svg and makes the animation to move left-to-right */
transform: scaleX(-1);
}
/* Just as thick as the original ring */
stroke-width: 7px;
/* Rounds the line endings to create a seamless circle */
stroke-linecap: round;
/* Makes sure the animation starts at the top of the circle */
transform: rotate(90deg);
transform-origin: center;
/* One second aligns with the speed of the countdown timer */
transition: 1s linear all;
/* Allows the ring to change color when the color value updates */
stroke: currentColor;
}
.base-timer__svg {
/* Flips the svg and makes the animation to move left-to-right */
transform: scaleX(-1);
}
This will output a stroke that covers the timer ring like it should, but it does not animate just yet to reveal the timer ring as time passes.
To animate the length of the remaining time line we're going to use the stroke-dash array property. we explains how it’s used to create the illusion of an element “drawing” itself.
5. Animate the progress ring
Now see how our ring will look like with different stroke-dasharray values:::Now !!! we can see that the value of stroke-dasharray is in-fact reducing our remaining time ring into equal-length parts, where the length is the time remaining value. That's happen when we set the value of stroke-dasharray to a one digit number (i.e. 1-9).
The name dasharray recommends that we can set multiple values as an array. Now view how it will behave if we set two numbers instead of one; in this case, those values are 10 and 30.
stroke-dasharray: 10 30 |
That sets the first part (remaining time) length to 10 and the second part (passed time) to 30. We can use that in our timer with a little trick. What we need initially is for the ring to cover the full length of the circle, meaning the remaining time equals the length of our ring.
What’s that length? Get out your old geometry textbook, because we can calculate the length an arc with some math:::
Length = 2πr = 2 * π * 45 = 282,6
That’s the value we want to use when the ring initially mounted. Let’s see how it looks.
That works!
OK, the first value in the array is our remaining time, & the second marks how much time has passed. What we want to do now is to manipulate the first value. Now see below what we can expect when we change the first value.
We will create two methods, one responsible for calculating what fraction of the initial time is left, and one responsible for calculating the stroke-dasharray value and updating the <path> element that represents our remaining time.
// Divides time left by the defined time limit.
function calculateTimeFraction() {
return timeLeft / TIME_LIMIT;
}
// Update the dasharray value as time passes, starting with 283
function setCircleDasharray() {
const circleDasharray = `${(
calculateTimeFraction() * FULL_DASH_ARRAY
).toFixed(0)} 283`;
document
.getElementById("base-timer-path-remaining")
.setAttribute("stroke-dasharray", circleDasharray);
}
function calculateTimeFraction() {
return timeLeft / TIME_LIMIT;
}
// Update the dasharray value as time passes, starting with 283
function setCircleDasharray() {
const circleDasharray = `${(
calculateTimeFraction() * FULL_DASH_ARRAY
).toFixed(0)} 283`;
document
.getElementById("base-timer-path-remaining")
.setAttribute("stroke-dasharray", circleDasharray);
}
We also necessity to update our path each second that passes. That's means we must call the early created setCircleDasharray method inside our timerInterval.
function startTimer() {
timerInterval = setInterval(() => {
timePassed = timePassed += 1;
timeLeft = TIME_LIMIT - timePassed;
document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
setCircleDasharray();
}, 1000);
}
timerInterval = setInterval(() => {
timePassed = timePassed += 1;
timeLeft = TIME_LIMIT - timePassed;
document.getElementById("base-timer-label").innerHTML = formatTime(timeLeft);
setCircleDasharray();
}, 1000);
}
Now we can see things moving !!!
Now, it work !!! but look closely, especially at the end. It looks like our animation is lagging by one second. When we reach 0 a small piece of the ring is still visible.
This is due to the animation’s duration being set to one second. When the value of remaining time is set to 0 (zero), it still takes one second to actually animate the ring to 0 (zero). We can get rid of that by decrease the length of the ring step-wise during the countdown. We do that in our calculateTimeFraction method.
function calculateTimeFraction() {
const rawTimeFraction = timeLeft / TIME_LIMIT;
return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction);
}
const rawTimeFraction = timeLeft / TIME_LIMIT;
return rawTimeFraction - (1 / TIME_LIMIT) * (1 - rawTimeFraction);
}
There we go!
There is one more thing. We said we should to change the color of the progress indicator when when the time remaining reaches certain position— sort of like letting the user know that time is almost up.
6. Changing the progress color at certain points of time
First of all, we need to add two thresholds that will indicate when we should change to the warning and alert states & add colours for each of that states. We are starting with green, then go to orange as a warning, ensue by red when time is nearly up.
// Warning occurs at 10s
const WARNING_THRESHOLD = 10;
// Alert occurs at 5s
const ALERT_THRESHOLD = 5;
const COLOR_CODES = {
info: {
color: "green"
},
warning: {
color: "orange",
threshold: WARNING_THRESHOLD
},
alert: {
color: "red",
threshold: ALERT_THRESHOLD
}
};
const WARNING_THRESHOLD = 10;
// Alert occurs at 5s
const ALERT_THRESHOLD = 5;
const COLOR_CODES = {
info: {
color: "green"
},
warning: {
color: "orange",
threshold: WARNING_THRESHOLD
},
alert: {
color: "red",
threshold: ALERT_THRESHOLD
}
};
Now create a method that’s responsible for checking if the threshold exceeded and changing the progress color when that happens.
function setRemainingPathColor(timeLeft) {
const { alert, warning, info } = COLOR_CODES;
// If the remaining time is less than or equal to 5, remove the "warning" class and apply the "alert" class.
if (timeLeft <= alert.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(warning.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(alert.color);
// If the remaining time is less than or equal to 10, remove the base color and apply the "warning" class.
} else if (timeLeft <= warning.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(info.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(warning.color);
}
}
const { alert, warning, info } = COLOR_CODES;
// If the remaining time is less than or equal to 5, remove the "warning" class and apply the "alert" class.
if (timeLeft <= alert.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(warning.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(alert.color);
// If the remaining time is less than or equal to 10, remove the base color and apply the "warning" class.
} else if (timeLeft <= warning.threshold) {
document
.getElementById("base-timer-path-remaining")
.classList.remove(info.color);
document
.getElementById("base-timer-path-remaining")
.classList.add(warning.color);
}
}
So, we’re simply removing one CSS class when the timer reaches a point and adding another one in its place. We’re going to need to define those classes.
.base-timer__path-remaining.green {
color: rgb(65, 184, 131);
}
.base-timer__path-remaining.orange {
color: orange;
}
.base-timer__path-remaining.red {
color: red;
}
color: rgb(65, 184, 131);
}
.base-timer__path-remaining.orange {
color: orange;
}
.base-timer__path-remaining.red {
color: red;
}
Here !!! there we have it. Here’s the demo again with everything put together.
Now it's your turn
If you like this amazing post then leave a comment below and tell is this helpful for you or not.{Sharing Is Caring} – If you believe in this then share this article now
Share if you believe in Sharing is Caring
0 Comments