How to Develop a Countdown Clock using Vue and Luxon (for Rockstars)
Screencast and Codepen Included
The countdown clock has been an integral part of my work for artists over the years, and just off the top of my head I recall developing clocks for Coheed and Cambria, Guns N Roses, Foo Fighters, Megadeth, Trivium, The Sword, Slipknot, and Ghost. It’s such a great example of UX because user’s understand it instantly, get excited, and are provided a time at which to return for a special announcement. Paired with an email capture, it is unstoppable.
Let’s use Vue.js and Luxon to first create a simple Countdown Clock engine and then I will provide a few inspirations on how you could make it unique.
We’ll be developing a single element countdown clock using the Vue.js Javascript framework. So let’s first include the Vue.js CDN link, add a single <main>
element with the id of “clock,” and finally initialize a Vue instance.
const app = new Vue({
el: '#clock'
})
Nice. Now we’re going to include Luxon which will make handling Javascript dates and durations much easier and preserve years of your life. I especially like that it comes with timezones out of the box which is crucial for my work. Include the minimized CDN link provided on their site and that will export a global variable of “luxon” we can use.
Now that we have both Vue and Luxon available, let’s begin by simply creating a clock of the current time. In order to do this, we’ll add the semantically friendly <time>
element to our app and reference the data property {{ now }}
within it. The double curly braces tells Vue to interpolate the data property of now into the view.
<time>{{ now }}</time>
Let’s declare that data property within our Vue instance using Luxon’s DateTime feature.
data() {
return {
now: luxon.DateTime.local()
}
}
The local() method tells Luxon to specify a date and time based on the user’s local timezone. Your app should now show the current date and time but it doesn’t update. In order to adjust the current time, we’re going to use a setInterval() method to update our now property every second. This method should be called once our Vue instance is loaded so we’ll add it to the mounted lifecycle hook.
mounted() {
setInterval(() => {
this.now = luxon.DateTime.local()
}, 100)
}
Nice. Your now data property will now be updated every 100 milliseconds. Before we move on, let’s add a data property called tick to our app and add it as a reference to our setInterval() method so we may use it to call clearInterval() once our countdown is complete.
data() {
return {
now: luxon.DateTime.local(),
tick: null
}
}mounted() {
this.tick = setInterval(() => {
this.now = luxon.DateTime.local()
}, 100)
}
Alright, the next thing we’ll want to do is add our ending time as a data property so we can compute the time remaining in our countdown. We’ll add another data property called end and use Luxon’s plus method to create an ending time which is 10 seconds in the future. This will be great for testing.
data() {
return {
now: luxon.DateTime.local(),
end: luxon.DateTime.local().plus({ seconds: 10 }),
tick: null
}
}
Now that we have the countdown’s ending time and an updating current time, we can compute the remaining time which should be displayed on our countdown. In order to do this, we’ll use Vue’s computed functionality to declare a property for the remaining time. Computed properties are great because they will reevaluate themselves based on their dependencies, which in our case is every time the current time changes. Within this property we’ll use Luxon’s diff method to get the difference between the end and current time and the toObject()
method to return that difference as milliseconds.
computed: {
remaining() {
return this.end.diff(this.now).toObject()
}
}
Now you could simply change {{ now }}
in your app’s view to {{ remaining }}
to display a dynamic data object containing milliseconds to your user but wouldn’t it be nice if this looked a bit more like a clock. In order to do this, let’s create another computed property called display and use Luxon’s Duration feature to create a duration object from our remaining milliseconds. We can then use Duration’s toFormat() method to put it into the format we desire. In this case, two digits for hours, minutes, and seconds separated by a colon.
computed: {
display() {
return luxon.Duration.fromObject(this.remaining).toFormat('hh:mm:ss')
}
}
Change the {{ now }}
in your view to {{ display }}
and you should see your countdown clock. If you let your clock tick down, you’ll notice that it does not stop at 00:00:00 and instead continues counting into negative digits. In order to prevent this, we’ll need to clear the setInterval() which is powering our clock once the countdown finishes. Let’s first add another computed property called finished which returns true or false if the current time is greater than the end time.
computed: {
finished() {
return this.now >= this.end
}
}
We now need to use this property somewhere to tell the interval when to stop. One place we can do this is within the setInterval method but I prefer to use a Vue watcher to react to changes to our current time property. Here’s how we can do that.
watch: {
now() {
if (this.finished) {
clearInterval(this.tick)
}
}
}
Now your clock should stop when it reaches 00:00:00. However, you might still see the clock tick to -1 seconds. This is simply because the times we are receiving from Luxon are exact and don’t start on 0 milliseconds. You have two choices here, you can update all of your DateTime.local() initializations to include a .set() method which makes sure the milliseconds are 0.
luxon.DateTime.local().set({ milliseconds: 0 })
Alternately you can simply update the finished() property to subtract one second from your end property.
finished() {
return this.now >= this.end.minus({ seconds: 1 })
}
The first solution makes sure your clock is ticking time in exact increments and the second is more of a visual hack. Depending on the design of your clock, either could be a nice fit. Before we jump into some basic styling, let’s update our HTML using Vue <template>
tags and conditional rendering to replace the countdown with a message once it is finished.
<main id=”clock”>
<template v-if=”finished”>
Boom
</template>
<template v-else>
<time>{{ display }}</time>
</template>
</main>
Congrats! You’ve built a countdown with Vue and Luxon. You can now style the countdown to your liking with some basic CSS. I went ahead and found a futuristic typeface called Orbitron on Google Fonts and imported it into my stylesheet alongside a few basic styles. This gives us a responsively sizing red countdown in the center of our page.
@import url('https://fonts.googleapis.com/css?family=Orbitron');html, body, main{
height: 100%;
width: 100%;
}body{
background: black;
color: #D53738;
font: 7vh 'Orbitron', sans-serif;
}main{
align-items: center;
display: flex;
justify-content: center;
}
Check out the completed clock on Codepen and fork it to make some adjustments of your own.
Going to 11
So what’s next? Well one thing that would be helpful for custom visualizations is simply a percentage amount of what point of the countdown we are at. Are we 10% in? 50%? This percentage can be used to great effect depending on the theme of your clock. Maybe an image is fully blurry when the countdown begins but comes into focus over the teaser. Perhaps you release one second of a song for each percentage of the countdown. That would be a seriously slow release of a track. Or maybe you simply want to draw a circle donut? Let’s try that.
In order to get a percentage of time elapsed we must first declare a start time. Up until this point we did not care about when the countdown started, only when it ended and what the current time was. If we declare a start time (perhaps the moment we share the countdown with our fans,) we can then figure out the total amount of time our countdown consists off and how much of the time has elapsed. I’m going to fork our original pen and first add a start property to our app’s data.
data() {
return {
start: luxon.DateTime.local()
}
}
For the sake of this example, we’ll say the countdown starts when the page loads. The next thing we’ll need is a couple of new computed properties we’ll be able to use in our design. First, we’ll add a total()
which is the total amount of time between the start and end times. Next, we’ll declare a elapsed()
property which computes the amount of time which has elapsed since the countdown started. Finally, we’ll create a percent()
property which divides the elapsed milliseconds by total milliseconds to give us a fraction we can multiply by 100 to compute our percentage.
computed: {
total() {
return this.end.diff(this.start).toObject()
},
elapsed() {
return this.now.diff(this.start).toObject()
},
percent() {
return this.elapsed.milliseconds / this.total.milliseconds * 100
}
}
Go ahead and remove any properties from within your <time> tag and add the following CSS to your stylesheet.
time{
border-radius: 50%;
height: 25vh;
width: 25vh;
}
This will turn the time tag into a squared round element which is 25% of the view height. We’re now going to add one final computed property but this one will be a bit different. Vue allows us to bind an elements style to a computed property so we’re going to compute a background CSS style that will create the dynamic donut effect we want. I found guidelines for this technique in this Sitepoint article and it uses the very experimental (and hardly supported) Conic Gradient. If you’re worried about support, check out the polyfill. Anyway, here’s our new computed property, gradient.
computed: {
gradient() {
background: `radial-gradient(black 60%, transparent 61%), conic-gradient(#D53738 0% ${this.percent}%, transparent ${this.percent}% 100%)`
}
}
A radial gradient of a 60% sized black circle is placed on top of a conic gradient which dynamically sizes a red color stop to the size of our countdown’s percent. The remaining conic-gradient will be transparent. Since this is a computed property, as our elapsed percentage changes so does the parameters of our conic gradient. I can’t wait for full browser support for this gradient type. The last thing you’ll want to do is bind your <time>
element to this new computed property:
<time :style="gradient"></time>
You should now see a red donut reveal over the time of your countdown. If you want to smooth the animation a bit, simply adjust your setInterval interval from 100 to 10 so the animation updates every 10 milliseconds.
I hope this provides a nice foundation for some countdown clock experiments. I look forward to seeing your clocks, count down.