Animated list with sliding background
Katie Adams
Posted on February 16, 2021
On Twitter recently, I announced the creation of my first ever codepen! 🎉
The point of the codepen was to solve a problem I encountered in a Vue project, wherein I'd been tasked with creating an animation I'd never done before. The brief was a pill-like shape that was animated to slide between items when a new one is selected (see the above codepen). CSS animation is not my strong suit; simultaneously learning Vue 3 and the composition API made an already new thing seem even more daunting.
Hence the codepen. My intention was to strip the process back and attempt to achieve the intended result for with good ol' plain JavaScript. And it seemed to work!
So how did it look once I'd translated it back into Vue? Well, it looked like this:
Let's dive a little further in and see what's happening.
The HTML is relatively straightforward to any Vue veteran. We've got an unordered list with a series of list items. Each list item is populated with the name of a Pokemon (lovingly taken from the PokeAPI. There's a couple of wrapper div
tags, predominantly for styling but one of them houses the ul
and a span
that will act as our coloured pill element.
<span
id="categoryBackground"
role="presentation"
class="transition-all duration-300 ease-in-out z-0 absolute rounded-full bg-red-700"
/>
This funky little dude is going to zoom around behind the various list items, happily animated and colourful. Note the role
attribute too, letting screen readers know that this is just for show.
Styling is done in Tailwind so I won't delve into that any more than is necessary.
So: the meaty stuff. The nitty gritty. The Javascript. Tasty stuff.
import { ref, computed } from "vue";
This line brings in some of the Composition API 'stuff' that is available in Vue 3. I recommend reading Dan Vega's post on the Ref and there's also some good documentation on Computed Refs too. Long story short, they're reactive. So if you find yourself using data from the VueX store where the content might change frequently, then your data should reflect it when we use these variable types.
The beauty of the computed
variable type is that it is reactive (just like the ref
) but it also keeps an eye on the data that it depends on. So when that dependent data changes, it updates itself! Pretty cool, right?
In our setup()
function, we define a few reactive variables:
- An array of
categories
, filled with Pokemon names -
selectedCategoryName
, a self-explanatory string selectedCategoryElement
-
categoryBackground
, which just returns our little decorative span element from the DOM -
selectedCategoryElement
will also return a DOM element but it does so using the selectedCategoryName to make sure it's picking up the element with the matching id.
We'll come back to the selectedCategoryElement
variable. It uses a function that is worth going over first:
function updateCategoryBackground(category) {
selectedCategoryElement = document.querySelector(
`#category${category.name}`
);
if (selectedCategoryElement && categoryBackground.value) {
categoryBackground.value.style.width =
selectedCategoryElement.scrollWidth + "px";
categoryBackground.value.style.height =
selectedCategoryElement.scrollHeight + "px";
categoryBackground.value.style.left = selectedCategoryElement.offsetLeft + "px";
}
}
This is our updateCategoryBackground()
function. This bad boy works the magic we're looking for with this animation. Firstly, it updates our selectedCategoryElement
variable with the DOM element of the clicked category. Then, provided that this new element actually exists and that our decorative span
was successfully found too, it updates the stylings of the latter to match the former! So if the Bulbasaur button is clicked, then our pill-shaped doodad will be told what size the button is and where it is, and he'll rush to copy.
Thanks to the Tailwind classes on the decorative span
, any transformations that occur on it - such as changes in size or position - are animated in an ease-in and ease-out fashion. Stupidly simple stuff but possibly not for someone who's never done it before.
So when does the updateCategoryBackground()
function even get called? Well, we've got another function called selectedCategoryChanged()
. Take another look at the unordered list in our template:
@click="selectedCategoryChanged(category)"
Each list item has an click event handler that uses - you guessed it - the selectedCategoryChanged()
function. This function updates the name of the selected value, thus updating the computed functions that rely on it. Then it calls the updateCategoryBackground()
function to move our funky little pill around the screen!
I purposefully left the selectedCategoryElement
variable until last because it does a couple of different things.
Vue.nextTick(() => {
updateCategoryBackground(
categories.value.find(
(cat) => cat.name === selectedCategoryName.value
)
);
});
As you can see, it calls the updateCategoryBackground()
function but is encapsulated in this Vue.nextTick()
arrow function. The nextTick()
function pushes back when the code runs. It waits until the DOM has rendered. This is important because the updateCategoryBackground
function updates the style attribute of our decorative span
. It's important that we know it will even be there to receive our update, otherwise we'll get a whole range of error messages.
Lastly, it returns the selected category from the DOM to ensure we have a default value when the app is first loaded. In this case, "Bulbasaur".
And that's it!
There's obviously a lot of ways this can be expanded to include different features and include different stylings. For instance, you can quite easily switch this up to include the usage of the Vuex store!
Let me know if you use this elsewhere or have a play yourself. It'd be great to see what improvements or changes get made!
Posted on February 16, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.