Vue Transitions and Animations

John Au-Yeung - May 7 '20 - - Dev Community

Like many JavaScript front-end frameworks, having the ability to display transition effects for various UI actions is a core feature of Vue.

With Vue, it’s easy to create animations for actions like adding and removing items to lists, or toggling an element on and off.

In this article, we'll look at some basic animation features that are built into Vue.js.

Basic Animation

We can create basic transitions by defining the CSS class that Vue looks for to create the transition effect and apply the style we want to it.

For transitions, the following classes will be used by Vue to style the transition effect:

  • v-enter: Start state to enter. This class is applied before the element is inserted and removed one frame after the element is applied.

  • v-enter-active: The class that's applied before the element is inserted into the DOM. This class is removed when the transition or animation finishes. It can also be used to define the duration, delay and easing curve for the entering transition. The easing is the rate of change of the element being animated.

  • v-enter-to: The class for the ending state for entering. It's added one frame after the element is inserted, which is the same time that the v-enter class is removed.

  • v-leave: This class is applied when the element starts leaving the DOM and when the leaving transition is triggered. The class is removed after one frame.

  • v-leave-active: This represents the active state for the leaving transition. It's applied during the whole leaving phase. It's added immediately when the leave transition is triggered and removed when the transition finishes. Use this class to define the duration, delay, and easing for the leaving transition.

  • v-leave-to: The ending state for the leave transition. This class is applied one frame after the leaving transition is triggered, which is the same time that the v-leave class is removed. This class is removed from the element being animated when the transition or animation is done.

v- is the prefix that can be replaced by the name of our animation, which we set as the value of the name prop of the transition component, which we'll use to add animation to our app.

For instance, we can create a simple transition effect that's displayed when we toggle an element on and off by writing the following code:

index.js

new Vue({
  el: "#app",
  data: {
    show: true
  }
});
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .fade-enter-active,
      .fade-leave-active {
        transition: opacity 0.3s;
      }
      .fade-enter,
      .fade-leave-to {
        opacity: 0;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="show = !show">
        Toggle
      </button>
      <transition name="fade">
        <p v-if="show">foo</p>
      </transition>
    </div>
    <script src="index.js"></script>
  </body>
</html>

The animation code is all in index.html. We added the Vue transition component that's built into the library.

In the same file, we have the CSS that's defined as follows:

.fade-enter-active,
.fade-leave-active {
    transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
    opacity: 0;
}

In the code above, we have the fade- prefix in each of our classes, which corresponds with the value of the name prop that's set in the transition component.

In each class, we just defined the styling that Vue looks for as we outlined above.

Then, when we click the Toggle button, we see that the word 'foo' will have a fading effect as it's being toggled on and off.

We can change the timing of the transition by adding easing as follows:

index.html

...
    <style>
      .fade-enter-active,
      .fade-leave-active {
        transition: opacity 1s cubic-bezier(1, 0.2, 0.8, 1);
      }
      ...
    </style>
...

In the code above, we have cubic-bezier(1, 0.2, 0.8, 1) which sets the rate of change of the opacity from coordinate from (1, 0.2) and (0.8, 1) by the cubic bezier curve directory.

The curve looks like the curve that's outlined in the Mozilla docs — we just have different coordinates.

We have eased in our code so that we can have a variable rate of change of the opacity. This results in transition effects that are more interesting than simple linear transitions.

Scaling Effects

In addition to fading effects, we can change our transition to resize our element instead of having a fading effect.

To make an effect that skew the p element, we can create an animation with the following example:

index.js

new Vue({
  el: "#app",
  data: {
    show: true,
    timer: undefined
  },
  beforeMount() {
    this.timer = setInterval(() => {
      this.show = !this.show;
    }, 1000);
  },
  beforeDestroy() {
    clearInterval(this.timer);
  }
});

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      p {
        font-size: 30px;
      }
      .scale-enter-active {
        animation: bounce-in 1s reverse;
      }
      .scale-leave-active {
        animation: bounce-in 1s;
      }
      @keyframes bounce-in {
        0% {
          transform: skewY(0deg);
        }
        25% {
          transform: skewY(2deg);
        }
        50% {
          transform: skewY(-5deg);
        }
        75% {
          transform: skewY(0deg);
        }
        100% {
          transform: skewY(-120deg);
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="show = !show">
        Toggle
      </button>
      <transition name="scale">
        <p v-if="show">foo</p>
      </transition>
    </div>
    <script src="index.js"></script>
  </body>
</html>

index.js is the same as the previous example.

In the code above, we have the bounce-in animation that transforms the p element by changing its angle with skewY

We apply the transition every second with the setInterval callback that toggles the this.show value between true and false after 1 second.

Custom Transition Classes

We can set our own transition class by passing in a few props to the transition component. They're the following:

  • enter-class
  • enter-active-class
  • enter-to-class (available since 2.1.8+)
  • leave-class
  • leave-active-class
  • leave-to-class (available since 2.1.8+)

They correspond to the transition stages that are outlined above.

We can use them as follows:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .enter,
      .leave {
        animation: bounce-in 0.5s;
      }
      .active,
      .leave-active {
        animation: bounce-in 0.5s reverse;
      }
      @keyframes bounce-in {
        0% {
          transform: skewY(0deg);
        }
        50% {
          transform: skewY(70deg);
        }
        100% {
          transform: skewY(150deg);
        }
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition
        enter-class="enter"
        enter-active-class="active"
        leave-class="leave"
        leave-active-class="leave-active"
      >
        <p v-if="show">foo</p>
      </transition>
    </div>
    <script src="index.js"></script>
  </body>
</html>

index.js is the same as the previous example.

In the code above, we specified our own class names for the transition stages.

We specified the class names as the value of the props, and then we used them in the style tag by specifying them with the animation.

Transition Durations

We can specify the duration prop to specify the duration of the transition effect.

For instance, we can use that as follows:

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .fade-enter-active,
      .fade-leave-active {
        transition: all 2s;
        transition-timing: ease-in-out;
      }
      .fade-enter,
      .fade-leave-to {
        opacity: 0;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <transition name="fade" :duration="2000">
        <p v-if="show">foo</p>
      </transition>
    </div>
    <script src="index.js"></script>
  </body>
</html>

index.js is the same as the previous example.

In the code above, we have :duration="2000" to make the transition duration 2s.

We can also specify the duration for the enter and leave phases separately by passing in an object as follows:

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .fade-enter-active,
      .fade-leave-active {
        transition: opacity 1s cubic-bezier(1, 0.2, 0.8, 1);
      }
      .fade-enter,
      .fade-leave-to {
        opacity: 0;
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="show = !show">
        Toggle
      </button>
      <transition name="fade" :duration="{ enter: 800, leave: 500 }">
        <p v-if="show">foo</p>
      </transition>
    </div>
    <script src="index.js"></script>
  </body>
</html>

We have :duration="{ enter: 800, leave: 500 }" to specify the duration for the enter phase as 0.8s and the leave phase as 0.5s respectively.

JavaScript Animations and Transition Hooks

We can attach event listeners to watch events that are emitted by various phases of the transition.

In each hook, we can get the element being animated with the el parameter of each hook.

The enter and leave hooks also have the done function that can be called to end the animation. The done function is needed to be called by JavaScript animations and is optional with CSS animations. Otherwise, the animation will be run synchronously.

These hooks are used for creating JavaScript animations mainly. We don't need them for CSS hooks.

We can create animations for Vue apps with the Velocity library.

index.js

new Vue({
  el: "#app",
  data: {
    show: true
  },
  methods: {
    beforeEnter(el) {
      el.style.opacity = 0;
      el.style.transformOrigin = "left";
    },

    enter(el, done) {
      Velocity(el, { opacity: 1, fontSize: "2.5em" }, { duration: 1300 });
      Velocity(el, { fontSize: "1em" }, { complete: done });
    },
    leave(el, done) {
      Velocity(
        el,
        { translateX: "25px", rotateZ: "270deg" },
        { duration: 1200 }
      );
      Velocity(el, { rotateZ: "220deg" }, { loop: 2 });
      Velocity(
        el,
        {
          rotateZ: "75deg",
          translateY: "50px",
          translateX: "30px",
          opacity: 0
        },
        { complete: done }
      );
    }
  }
});

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
  </head>
  <body>
    <div id="app">
      <button @click="show = !show">
        Toggle
      </button>
      <transition
        @before-enter="beforeEnter"
        @enter="enter"
        @leave="leave"
        :css="false"
      >
        <p v-if="show">foo</p>
      </transition>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have the Velocity library so that we can create JavaScript animations with the DOM object that's in the parameter.

We styled the p element in the beforeEnter hook to style it when the transition starts.

We set the p element's opacity to 0 and the base placement of the element with the transformOrigin property to 0.

In the enter hook, we set the opacity and the font size when the p element is being inserted into the DOM. We also add our styling to the p element when it's removed from the DOM.

We added some rotation effects and some font size changes to make our p element look more interesting when clicking on Toggle to remove the p element when it's present in the DOM.

The done function that's in the parameter for enter and leave methods is used as a callback for when the animation is complete.

List Transitions

To add transitions for v-if, we can also add transitions for when an item that's being rendered by v-for is being added and removed from the DOM.

Instead of the transition component, we use the transition-group component to apply the transition effect.

For instance, we can create a transition effect to show an animation when items are being added and removed from a list as follows:

index.js

new Vue({
  el: "#app",
  data: {
    items: [1, 2, 3, 4, 5]
  },
  methods: {
    randomIndex() {
      return Math.floor(Math.random() * this.items.length);
    },
    add() {
      this.items = [...this.items, ++this.items[this.items.length - 1]];
    },
    remove() {
      this.items.splice(this.items.length - 1, 1);
    }
  }
});

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
      .list-enter-active,
      .list-leave-active {
        transition: all 1s;
      }
      .list-enter,
      .list-leave-to {
        opacity: 0;
        transform: translateY(40px);
      }
    </style>
  </head>
  <body>
    <div id="app">
      <button @click="add">Add</button>
      <button @click="remove">Remove</button>
      <transition-group name="list" tag="p">
        <span v-for="item in items" :key="item">
          {{ item }}
        </span>
      </transition-group>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we defined the styling for the transition phases as follows:

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

The transition phases are the same as the v-if transitions. We can apply styles when an element is being added and removed.

Once again, the name of the name prop matches the name- prefix of the CSS code.

The tag prop lets us specify the tag of the wrapper element. In our example, we make the wrapper element the p element.

State Transitions

We can also animate component state changes. To make this easy, we use the GreenSock library.

For instance, we can create an element that animates the number that we entered into the input as we change it, as follows:

index.js

new Vue({
  el: "#app",
  data: {
    number: 0,
    tweenedNumber: 0
  },
  computed: {
    animatedNumber() {
      return this.tweenedNumber.toFixed(0);
    }
  },
  watch: {
    number(newValue) {
      gsap.to(this.$data, { duration: 0.5, tweenedNumber: newValue });
    }
  }
});

index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.2.4/gsap.min.js"></script>
  </head>
  <body>
    <div id="app">
      <input v-model.number="number" type="number" step="20" />
      <p>{{ animatedNumber }}</p>
    </div>
    <script src="index.js"></script>
  </body>
</html>

In the code above, we have the number watcher that calls:

gsap.to(this.$data, { duration: 0.5, tweenedNumber: newValue });

to animate the number as it's being changed.

Conclusion

Creating transitions is easy with Vue. We can easily create them with the transition component for v-if transitions.

To animate v-for transitions, we can use the transition-group component.

We can create CSS transitions by prefix-specific classes with the value that we pass in as the value of the name prop.

Also, we can create JavaScript animations with the listeners that we can add to our components and we can use the Velocity library to add JavaScript animations.


Now that you have covered Vue animations, check out our other in-depth Vue tutorials — including our tutorial about protecting your source code.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .