1 - Teaching myself (and you) about Vue animations

We had to do a vote at work about what JS framework we would like to pick as the “official” one to used by everybody. I came from more of a React background and my choice was React. I even ran a couple of workshops for my colleagues to show them how awesome React is, secretly hoping I would influence their votes in its favour, but to my then sad surprise, Vue got the majority vote.

After spending a few moments sulking about it, I decided to try building a tiny Vue app, using just the Vue documentation. Building the app was so fun and straightforward, it got me instantly hooked on Vue, and after several projects that I used Vue to write the UI in, I can now admit with an open heart that I am now fully converted to a Vue fanboy.

Today we’re going to talk about how animations work in Vue.

Normally, when you hear animation, you could instantly think of CSS animations that you write, hook them up to a CSS class and you would use JavaScript to dynamically toggle add / remove the class on certain events. At least this is what pops in my head when it comes to animations in UI, and more or less, this has been the way I have been implementing animations in my UI. Of course like anything in programming, you can go as simple or as complicated as your heart desires, but our use case we’re going to be discussing today is adding simple animations that enhance 2 main types of events:

— Showing / Hiding a piece of UI

— Animating piece of UI when they are dynamically added to the UI (think about adding items to a list)

Let’s not waste any time and dive straight into it!

Vue animations high level overview:

Vue has a variety of ways out-of-the box that you can use to apply transition effects when items are:

  • inserted
  • updated
  • removed

Vue will automatically apply CSS classes you can use to add CSS transitions and animations, and you can also use JavaScript to directly manipulate the DOM during transition hooks.

Let’s talk about how we can transition / animate a single element / component: I am a visual and hands-on learner, so let’s hope on CodePen and write some code!

First, let’s set up our template:

<div id="app">
  <button @click="toggleHide">
    Toggle
  </button>
  <div v-if="visible" class="awesomeTransition">
      Hey there! I'm an awesome element!
  </div>
</div>

And the JS that will power it up:

const app = new Vue({
  el: '#app',
  data() {
    return {
      visible: false,
    };
  },
  methods: {
    toggleHide() {
      this.visible = !this.visible;
    },
  }
});

As you can see, there is nothing scary about that code. We have an extremely simple component, with one single property called visible, that is simply a boolean we will use to show or hide our awesomeTransition. Our component has a very simple toggleHide method that will change our visible property to true or false. We hook our button in the template to trigger our toggleHide method on click, dd some CSS to style it up any way we like and away we go!

Here is how our component looks like so far:

component start

Ok, so that’s pretty cool, we managed to get our awesome component to show / hide based on the visible property, and we can change that property just by clicking a button. But it won’t win us any design awards just yet. Let’s take this bad boy to the next level and give it a smooth ass animation when it shows and hides.

Enter the Vue <transition> component!

Vue offers a <transition> component that we can use to wrap our cool element inside, which will give us all the CSS and JS hooks we need to implement some cool animations. You can set it up before you can say “blueberry pancakes”:

<div id="app">
  <button @click="toggleHide">
    Toggle
  </button>
  <transition name="awesome">
    <div v-if="visible" class="awesomeTransition">
      Hey there! I'm an awesome element!
    </div>
  </transition>
</div>

Well that was easy right! All we did was wrap it in <transition> and we gave the transition a name. We need to give the transition a name, because Vue will expose that name for us to use in our JS and CSS.

Before we continue animating this bad boy, let’s do a quick rundown on the transition states. When we wrap an element inside a <transition> tag and give it a name, that transition tag will give us some hooks to use. The hooks we care about the most, and will be using the most are:

  1. enter / v-enter - This is the start. You can read that as “what do I want the state of my component to be when it enters?” Here we do all of our set up for how the component will look at the start. Here we can say we want to give it a 0 opacity and shift it a little bit down.
  2. enter-active / v-enter-active - This is the active phase of the transition. This is where we will be defining our duration, the easing curve and the transition property to decide what exactly we want to transition.
  3. enter-to / v-enter-to - Ending state for enter. Added one frame after element is inserted (at the same time v-enter is removed), removed when transition/animation finishes. If enter is the start, then enter-to is the end. We won’t be using enter-to that much, as enter-active covers the entire state of the transition, but it’s nice to know what enter-to does.
  4. leave / v-leave - the oposite of enter, this is the the starting state for leave, added immediately when our transition is triggered;
  5. leave-active / v-leave-active - same as enter-active, this is the active phase of the transition. It’s applied during the entire phase of the leave, and here we define how we want our duration, ease curve and what we want to transition.
  6. leave-to / v-leave-to - The ending state for leave. This is where we say “how do I want my element to look like when it’s finished transitioning out?”

Now that we have an understanding of the transitions states that come with the <transition> component, let’s add some simple CSS transitions and see them in action!

To use the states within our CSS, all we have to do is define a class with a prefix with the name of the <transition> component we used to wrap our elements in!

.awesome-enter-active, .awesome-leave-active {
  transition: all .5s;
}

Here we are using the enter-active and leave-active states to define the same duration and timing on our elements when they both enter and leave the UI. One bird with two stones, or was it the other way around? If we could translate this to the computer in pure english it would sound like “Hey, make sure that when something gets changed when the component appears and disappears it transitions everything and it should take 500ms. Also can you get me a coffee on your way there?”

.awesome-enter, .awesome-leave-to {
  opacity: 0;
  transform: translateX(10px);
}

And here we use enter and leave-to to say how we want our component to look before it appears and after it disappears! We make it invisible and push it down with 10 pixels. Easy peasy!

Now let’s see how this bad boy looks now:

component start

Oh baby! Look at how smooth this is! Ok maybe it doesn’t look that smooth on here, because it’s a gif recorded from my computer, but you can check it out on CodePen below!

Animating list entries:

By using our understanding gained from transitioning a single piece of ui based on show / hide, we can easily understand how list elements can be animated in Vue. To get how that works, we only need to use one slightly different component.

As before, let’s set up our template and logic, starting with the template:

<div id="app">
  <input type="text" v-model="currentComment">
  <button @click="addComment">add comment</button>
  <div class="comments">
    <transition-group name="list" tag="div">
      <single-comment v-for="comment in comments" :comment-data='comment' key="comment"/>
    </transition-group>
    
  </div>
</div>

<script type="text/x-template" id="single-comment">
  <div class='comment' :style="{backgroundImage: `url(${commentData.image})`}">
    <h3>
      {{ commentData.text }}
    </h3>
  </div>
</script>

And the JS that powers it:

Vue.component('single-comment', {
  template: '#single-comment',
  props: ['commentData']
});

const App = new Vue({
  'el': '#app',
  data () {
    return {
      currentComment: 'Replace me',
      comments: [],
      counter: 10,
    }
  },
  methods: {
    addComment() {
      this.counter += 10;
      this.comments.push({
        text: this.currentComment,
        image: `https://source.unsplash.com/800x${600 + this.counter}`,
      });
    }
  }
});

We have a very simple component here that has an array called comments in its state, to which we will append comments, and then render in the UI as we add them with a simple v-for loop. We want to be able to animate when we add a new item to the list, and the difference between animating a single element and animating addition to a list, is that we don’t use the <transition> tag, but <transition-group> in stead.

Just like <transition>, we give it a name to refer to in our CSS, and the other difference is that we specify a tag attribute on it. Whatever you put in that tag, you get that HTML element rendered in the DOM. Here’s how our looks like:

<transition-group name="list" tag="div">

Easy right?

Now let’s add some CSS to define how we want the list elements to look like before and after they are inserted in the UI:

.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to {
  opacity: 0;
  transform: translate(30px, 20px);
}

Nothing different here! We should already know what this code does from before!

Let’s see that in action!

lists

Pretty smooth right!

This is the meat and potatoes of animating elements in Vue, it is pretty straightforward and your limit is your imagination - remember that we can define whatever we want in the CSS, we can use @keyframes if we want to have more advanced animations, there is no limits!

I hope this has been useful, and I am looking forward to the next blog I write.

References:

Animating single elements

Animating lists

Vue documentation

Until next time!


Written by Emil Mladenov - a slavic software developer who decided to use a blog as a digital rubber duck

I also have a podcast