Last week I shared my approach for creating a simple calendar in CSS grid for a client project I was wrapping up. As it turns out, there is another aspect of this project’s UX that I would love to share. First, a little background.

The project itself is a simple app for the 60th anniversary of Motown Records which will be live soon. When Motown shared all of their incredible content with me, I was drawn to a doc filled with daily facts about Motown’s history. The client and I agreed that a simple mobile friendly web app which surfaced “today’s” facts would be perfect. I built a basic version quickly and ran into a simple UX issue. How was I going to navigate between stories if a day included multiple facts? I jumped into Figma to figure out a solution and first presented a little “dot” navigation but thought it felt boring.

I then thought about existing user patterns I could borrow and my mind locked onto the Instagram Story Auto-Play UX or whatever they call it. That’s the little set of progress bars at the top of an Instagram story which show you which story in the set you are currently focused on. In addition, it shows the progress and duration of the current story and the point at which it will jump to the next story. I also think it’s important to include the touch events when emulating this experience:

A tap on the left side of the screen jumps to the previous story

A tap on the right side of the screen jumps to the next story

Pressing to hold pauses the currently playing story

Letting go of a press resumes story playback

Let’s recreate a simple version of this UX using Vue, Anime.js, and Hammer.js. You can check out the Codepen and read on for a breakdown.

Setting up the app

Let’s first add a <main> element to our page with the id of “app” and include the Vue.js CDN link. You can then initialize your Vue app as follows:

const app = new Vue({

el: '#app',

data() {

return {

colors: ['#D53738', '#638867', '#FAF429']

}

}

})

For this example, we’ll be jumping from one color to another based on our array of colors. So let’s add a v-for loop within our <main> to create a set of sections for each of our colors.

<section

v-for="(color, index) in colors"

:key="index"

:style="{ background: color }"

>

{{ color }}

</section>

This handy v-for directive from Vue can render a list of items from an array. In our case, we’re creating one <section> for each of our colors and using a Vue style binding to give it a background of the associated color. Awesome. Like Instagram Stories, we will only want to show one of these colors at a time. Let’s first do that by adding a new parameter to our data object called current and give it the value of 0. You can then expand on your <section> to include a v-if directive which only shows the section if its index equals the value of current.

v-if="current == index"

Once you add that, only the first color should be shown. Now let’s add a bit of CSS which sizes the <section> to fill the screen. Something like this should work:

html, body, main, section{

height: 100%;

width: 100%;

}

Note: I always include a reset in my Codepen which quickly removes any sort of padding or margin styles a browser brings with it by default. You will also want to add this to your <head> so it looks nice on mobile.

<meta name=”viewport” content=”width=device-width, initial-scale=1">

Now we have a full page of color thanks to a couple of Vue directives and CSS styles. Nice. You can remove the {{ color }} variable from within the section now as we don’t need it. Next thing we’ll want to do is create the little nav of progress bars. Turns out we can use a very similar v-for loop to accomplish this.

<nav>

<div v-for="color in colors">

<div></div>

</div>

</nav>

Simple stuff. The real magic happens in the CSS. First, let’s style our <nav> .

nav{

box-sizing: border-box;

display: grid;

grid-column-gap: 1em;

grid-template-columns: repeat(3, 1fr);

height: 0.5em;

padding: 0 1em;

position: fixed;

top: 1em;

width: 100%;

}

This fixes the nav to the top of the page and tells the internal divs to render in a three column grid with 1em of spacing between each column. Now you might be wondering what happens if there are more or less than three colors. Good question. This will break. In my production app, I decided to add a style binding which dynamically changes the grid columns based on the amount of stories or colors.

<nav

:style="{ gridTemplateColumns: `repeat(${colors.length}, 1fr)` }"

>

Thank you Vue. Next up, we’ll need to style the internal divs but that is quite simple. First, add this to your CSS:

nav > div{

background: rgba(0,0,0,0.25);

height: 100%;

} nav > div > div{

background: black;

height: 100%;

width: 0%;

}

The initial internal divs will receive a transparent black background and within each of these will be a div colored opaque black. The opaque div will be initially set to 0% width but that will adjust as the timeline progresses.

That’s it for the HTML and CSS. Let’s jump into the Javascript and begin animating this thing.

Animation

First, include Anime.js in your project. Anime.js a light-weight but powerful javascript animation library. I don’t usually get to these delighters in my projects but when I do, I prefer Anime.js for simple tweening. First, we’ll create a timeline:

mounted() {

let timeline = anime.timeline({

autoplay: true,

duration: 10000,

easing: 'linear',

loop: true

})

}

This is simple enough. The timeline will autoplay with a default duration of 10 seconds at a linear rate and loop forever. Now let’s add an animation to the timeline for each color right below the timeline initialization within mounted() .

this.colors.forEach((color, index) => {

timeline.add({

targets: document.querySelector('nav > div')[index].children[0],

width: '100%',

changeBegin: (a) => {

this.current = index

}

})

})

This tells Anime.js to add a timeline animation for each of the opaque divs, animating their width to 100%. As each animation begins, we adjust our current color parameter to the current animation’s index. This allows the color to change as the animation plays through. You’re close now! Let’s just add in some touch events to control the timeline.

Touch Events

Hammer.js is a rad library that adds touch gestures to your application. It’s also named after MC Hammer who once tweeted kind words to me. True story! Include it in your project and lets first add the press events.

let hammertime = new Hammer(document.querySelector('#app')) hammertime.on('press', (e) => {

timeline.pause()

}) hammertime.on('pressup', (e) => {

timeline.play()

})

This couldn’t be easier, thanks to Hammer’s listener events and the Anime.js control functions. Try it out by clicking and holding to pause and then releasing to resume play. Finally, it would be nice if our app navigated next and previous to colors depending on which side of the screen we tapped on. Let’s use the Hammer.js tap event to achieve this.

hammertime.on('tap', (e) => {

if (e.center.x > window.innerWidth / 2) {

if (this.current < this.colors.length - 1) {

this.current += 1



this.timeline.pause()

this.timeline.seek(this.current * 10000)

this.timeline.play()

}

} else {

if (this.current > 0) {

this.current -= 1



this.timeline.pause()

this.timeline.seek(this.current * 10000)

this.timeline.play()

}

}

})

When the tap event is called, we check the x position of the tap and see if it occurs on the left or right side of the screen. If it’s the right side of the screen and we’re not already on the final story, we’ll first increment our current color variable by one. We’ll then seek our timeline animation to the starting point of that colors progress which you can declare by multiplying the color index by the animation duration. We’ll do the same for the left side of the screen and make sure the user doesn’t go into negative colors which do not exist.