Using Vue Computed Properties to Count Down to New Armin van Burren
Tags: vue, music, howler, nuxt
Published: 2024-03-24 17:07:11

I’ve always thought of the countdown clock as a mainstay digital marketing tactic and I’ve developed a lot of them in my career. Maybe 50? A slow ticking clock builds anticipation but also helps establish “when” something will happen. And, isn’t that what we’re trying to do? Increase hype and awareness through thematic means and reward fans for their attention. 

A few weeks back Janice Renée Wendel and Lucas Wijkhuizen from [Armada Music](https://www.armadamusic.com) contacted me about an upcoming [Armin van Buuren](https://en.wikipedia.org/wiki/Armin_van_Buuren) and [Gryffin](https://en.wikipedia.org/wiki/Gryffin) track “What Took You So Long.” The [Joris van Meegen](https://www.instagram.com/jorisvanmeegen/) designed single visual includes a sort of ancient timepiece on top of a geometric vector design. While the first thing that came to mind was a rather complicated “sundial” concept I’ve been shopping around since the Daniel Ceasar [campaign](https://www.leemartin.com/mate-in-one), the limited time we had led us to a simpler, countdown “clock” concept. An obvious start would be animating the hand/needle/gnomon on the timepiece so it makes one revolution from the start of our campaign to the track releasing. However, once you have a start and end time, you also gain a “percentage” of the time elapsed. What if we also included a preview clip of the song and the amount of audio you get to listen to is connected to how much of the countdown has elapsed? Using the outer edge of the geometric design, we established a series of time “segments” which unlock over time and in turn unlock more of the audio preview. It’s a simple tactic which will hopefully increase anticipation.

In an effort to generate some pre-saves, this activation is hidden behind a [Feature.fm](https://www.feature.fm) presave, which provides a post pre-save redirect url. So, once a user successfully presaves the track via their DSP of choice, Feature redirects them to our clock. Presave “What Took You So Long” today to enjoy an [evolving preview](https://www.whattookyousolong.net) of the upcoming release and read on to learn how it came together.

## Development

{% embed https://vimeo.com/926852800 %}

A Vue [computed property](https://vuejs.org/guide/essentials/computed) is defined as a data property which depends on another property. It is a surprisingly simple and powerful expression which allows you to compute all sorts of useful properties based on the values of another. In the case of this app, "time" drives the computation of all of the properties that make our clock tick. Let's start from the beginning.

### Countdown

Every countdown clock has a current (now) time and the time we are counting down to. Our clock also requires a start time so we can determine the percentage time passed since the start of our campaign. We can define these easily using the [ref](https://vuejs.org/guide/essentials/reactivity-fundamentals.html#ref) function and use [Date.UTC](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/UTC) to make sure we're counting down to the same time regardless of the user's timezone.

```javascript
// Start
const start = ref(new Date(Date.UTC(2024, 2, 15, 8, 0, 0)))

// Now
const now = ref(new Date())

// End
const end = ref(new Date(Date.UTC(2024, 2, 21, 23, 0, 0)))
```

Now, let's declare the most obvious computed property: `finished`. This will help us determine if the countdown has completed. All we need to do is check to see if the current time is greater than or equal to the countdown ending time.

```javascript
// Finished
const finished = computed(() => {
  // If now greater than end
  return now.value >= end.value

})
```

In order to tick the clock forward, we simply need to start an interval which ticks every second (or sooner, depending on your use case.) Within this interval, the now value is updated and if the countdown is finished, we can stop ticking.

```javascript
// Start tick
function startTick() {
  // Set tick interval
  tick = setInterval(() => {
    // Update now
    now.value = new Date()

    // If finished
    if (finished.value) {
      // Stop tick
      stopTick()

    }

  }, 1000)

}

// Stop tick
function stopTick() {
  // Clear interval
  clearInterval(tick)

}
```

Now, let's compute the properties which drive our clock visual.

### Clock

![What Took You So Long Artwork](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b5wzyjjj7jop6kf7gnfa.jpg)

As I mentioned in the intro, the clock image has a series of 32 segments near the outer circular edge. I thought it would be nice to fill these up visually as time passed. In order to do that, we'll need to compute some properties. First, let's computed the time span of the entire countdown. This is the overall time from the start of activation to its completion.

```javascript
// Time span
const timeSpan = computed(() => {
  // End minus start
  return end.value - start.value

})
```

We'll also need the time elapsed, which is how much time has *elapsed* since the countdown begun.

```javascript
// Time elapsed
const timeElapsed = computed(() => {
  // Now minus start
  return now.value - start.value

})
```

It will also be helpful to know the percentage of time elapsed for visual purposes. Note how this property is deriving from other computed properties. This is where computed properties get super powerful.

```javascript
// Time percent
const timePercent = computed(() => {
  // Elapsed time divided by timespan
  return timeElapsed.value / timeSpan.value

})
```

There are 32 time segments on the clock image so let's determine how much time each of these segments take. In order to do that, we simply divide the entire span of time by the amount of segments.

```javascript
// Time segment
const timeSegment = computed(() => {
  // One segment of timespan
  return timeSpan.value / segments.value

})
```

Now that we know how much time makes up a time segment, we can determine which segment we're on by getting the remainder of the elapsed time and the end time. Then, dividing that value by the time segment value. Finally, we'll use floor to round down to the largest integer.

```javascript
// Current segment
const currentSegment = computed(() => {
  // Current unlocked segment
  return Math.floor((timeElapsed.value % end.value) / timeSegment.value)

})
```

You can visualize the *filling* of segments many ways, depending on your user case. In the end, I created a little [component](https://gist.github.com/leemartin/d314b14f1817fd8075f49b98f613db08) which uses a CSS conic gradient to create a sort of donut chart. You could also do this using canvas, especially if you're keen to create a dynamic image of the countdown for sharing.

The last bit of the clock visual is rotating the clock hand. We can do this by simply multiplying the percentage of time elapsed by 360 (degrees.) Don't worry about `seek`. We'll get into that next when talking about the audio player.

```javascript
// Clock rotation
const clockRotation = computed(() => {
  // If seek is 0
  if (seek.value == 0) {
    // Time angle
    return timePercent.value * 360

  } else {
    // Seeked angle
    return seekPercent.value * 360

  }

})
```

Then, all we need to do is use CSS to rotate the clock hand image. Shout out to Vue for [style bindings](https://vuejs.org/guide/essentials/class-and-style.html) also. 

```html
<div id="hand" :style="{ transform: `rotate(${clockRotation}deg)` }"></div>
```

Now that we have a ticking clock. Let's bring some of this countdown logic into a dynamic audio player.

### Player

In addition to the clock, it was our ambition to add an audio player which allowed users to play a preview of the upcoming song and the amount of preview they could listen to was connected to the amount of time elapsed on the countdown. So, as the countdown ticked closer to release, they were able to hear more of the track. 

First, let's load the track with our audio library of choice: [Howler.js](https://howlerjs.com). Keeping track of the track's `duration` is important. We'll get into the `startListening` and `stopListening` methods soon.

```javascript
sound = new Howl({
  src: ["/what-took-you-so-long.mp3"],
  html5: true,
  onload: () => {
    // Update duration
    duration.value = sound.duration()

  },
  onplay: () => {
    // Start listening
    startListening()

  },
  onend: () => {
    // Stop listening
    stopListening()

  },
  onpause: () => {
    // Stop listening
    stopListening()

  },
  onstop: () => {
    // Stop listening
    stopListening()

  }
})
```

Now that we have the track's duration, we can computed the audio segment equivalent by diving the duration by the total amount of segments (32.) We'll use this to visualize the preview playback on the clock also.

```javascript
// Audio segment
const audioSegment = computed(() => {
  // One segment of audio
  return duration.value / segments.value

})
```

As the track plays, a `seek` property is updated to represent the current position in time. (We'll set this in a different method.) We can use seek and duration to determine which *segment* the playback is on. Again, this is the audio player equivalent to the clock segment visual.

```javascript
// Seek segment
const seekSegment = computed(() => {
  // Current seeked segment
  return Math.floor((seek.value % duration.value) / audioSegment.value)

})
```

Another computed property we'll need is determining how much of the audio track is unlocked for listening. This is determined simply by multiplying the duration of the track by the percentage of time elapsed.

```javascript
// Audio unlocked
const audioUnlocked = computed(() => {
  // Time percent times duration
  return duration.value * timePercent.value

})
```

With all these properties in place, we'll need a method that is constantly updating the `seek` time when a track is playing and checks to see if the current seek is greater than the unlocked time. If so, we'll stop the track. Let's just call it `listen()`.

```javascript
// Listen
function listen() {
  // Request animation frame
  frame = requestAnimationFrame(listen)

  // Update
  seek.value = sound.seek()

  // If seek is greater than unlocked
  if (seek.value >= audioUnlocked.value) {
    // Stop sound
    sound.stop()

  }

}
```

And back in the Howler.js setup, we start and stop *listening* depending on if the track is playing. We'll use [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) to update things as fast as the browser wishes for us to.

```javascript
// Start listening
function startListening() {
  // Request animation frame
  frame = requestAnimationFrame(listen)

}

// Stop listening
function stopListening() {
  // Cancel animation frame
  cancelAnimationFrame(frame)

}
```

Now, when a user plays the track, the listener will start and as soon as the seek meets the unlocked value, the track will stop.

## Acknowledgements

Thanks again to Janice Renée Wendel, Lucas Wijkhuizen and their team at Armada Music for this opportunity. Their attention to detail is unrivaled and they entered this project with a great sense of excitement to do something special for Armin’s fans. I’ll gladly make time for them.