loading

Vue Animations

When elements are added or removed using v-if, v-show, or dynamic components, the built-in <Transition> component in Vue facilitates the creation of animations.

In other situations, there’s nothing wrong with employing standard CSS animations and transitions.

A Short Introduction to CSS Transition and Animation

For this section of the training, a basic understanding of CSS transitions and animations is required.

However, before we develop animations using the built-in <Transition> component particular to Vue, let’s take a look at two instances of basic CSS transitions and animations that work with Vue.

Example

App.vue:

				
					<template>
  <h1>Basic CSS Transition</h1>
  <button @click="this.doesRotate = true">Rotate</button>
  <div :class="{ rotate: doesRotate }"></div>
</template>

<script>
export default {
  data() {
    return {
      doesRotate: false
    }
  }
}
</script>

<style scoped>
  .rotate {
    rotate: 160deg;
    transition: rotate 1s;
  }
  div {
    border: solid black 2px;
    background-color: lightcoral;
    width: 60px;
    height: 60px;
  }
  h1, button, div {
    margin: 10px;
  }
</style>
				
			

In the example above, we apply a class to the <div> element using v-bind, causing it to rotate. Because the rotation is defined using the CSS transition feature, it takes one second to complete.

The CSS animation property can be used to move an object, as demonstrated in the example below.

Example

App.vue:

				
					<template>
  <h1>Basic CSS Animation</h1>
  <button @click="this.doesMove = true">Start</button>
  <div :class="{ move: doesMove }"></div>
</template>

<script>
export default {
  data() {
    return {
      doesMove: false
    }
  }
}
</script>

<style scoped>
  .move {
    animation: move .5s alternate 4 ease-in-out;
  }
  @keyframes move {
    from {
      translate: 0 0;
    }
    to {
      translate: 70px 0;
    }
  }
  div {
    border: solid black 2px;
    background-color: lightcoral;
    border-radius: 50%;
    width: 60px;
    height: 60px;
  }
  h1, button, div {
    margin: 10px;
  }
</style>
				
			

The < Transition > Component

Using simple CSS animations and transitions, as we did in the last two examples, is perfectly acceptable.

Fortunately, Vue comes with a built-in <Transition> component that we can use to animate an element when it is added or removed from our application using v-if or v-show. This is something that would be difficult to accomplish with simple CSS animation.

First, let’s create an application where a button adds or removes a paragraph mark (<p>).

Example

App.vue:

				
					<template>
  <h1>Add/Remove <p> Tag</h1>
  <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  <p v-if="exists">Hello World!</p>
</template>

<script>
export default {
  data() {
    return {
      exists: false
    }
  },
  computed: {
    btnText() {
      if(this.exists) {
        return 'Remove';
      }
      else {
        return 'Add';
      }
    }
  }
}
</script>

<style>
  p {
    background-color: lightgreen;
    display: inline-block;
    padding: 10px;
  }
</style>
				
			

Let’s now encircle the <p> element with the <Transition> component to observe how we may animation the <p> tag being removed.

The <Transition> component comes with six different CSS classes that we may use to animate the addition or removal of components.

In the example below, we will create a fade-out animation when the <p> tag is deleted by utilizing the automatically accessible classes v-leave-from and v-leave-to:

Example

App.vue:

				
					<template>
  <h1>Add/Remove <p> Tag</h1>
  <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  <Transition>
    <p v-if="exists">Hello World!</p>
  </Transition>
</template>

<script>
export default {
  data() {
    return {
      exists: false
    }
  },
  computed: {
    btnText() {
      if(this.exists) {
        return 'Remove';
      }
      else {
        return 'Add';
      }
    }
  }
}
</script>

<style>
  .v-leave-from {
    opacity: 1;
  }
  .v-leave-to {
    opacity: 0;
  }
  p {
    background-color: lightgreen;
    display: inline-block;
    padding: 10px;
    transition: opacity 0.5s;
  }
</style>
				
			

The Six < Transition > Classes

The <Transition> component makes six classes instantly available to us.

We can use the first three classes to animate the transition when an element is added to the <Transition> component:

v-enter-from
v-enter-active
v-enter-to

Additionally, we can utilize the following three classes as an element inside the <Transition> component is removed:

v-leave-from
v-leave-active
v-leave-to

Note: The <Transition> component can only have one element at the root level.

Now, let’s employ four of these classes to enable animation for both the addition and removal of the <p> tag.

Example

App.vue:

				
					<template>
  <h1>Add/Remove <p> Tag</h1>
  <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  <Transition>
    <p v-if="exists">Hello World!</p>
  </Transition>
</template>

<script>
export default {
  data() {
    return {
      exists: false
    }
  },
  computed: {
    btnText() {
      if(this.exists) {
        return 'Remove';
      }
      else {
        return 'Add';
      }
    }
  }
}
</script>

<style>
  .v-enter-from {
    opacity: 0;
    translate: -100px 0;
  }
  .v-enter-to {
    opacity: 1;
    translate: 0 0;
  }
  .v-leave-from {
    opacity: 1;
    translate: 0 0;
  }
  .v-leave-to {
    opacity: 0;
    translate: 100px 0;
  }
  p {
    background-color: lightgreen;
    display: inline-block;
    padding: 10px;
    transition: all 0.5s;
  }
</style>
				
			

The v-enter-active and v-leave-active properties can also be used to establish style or animation when an element is added or removed:

Example

App.vue:

				
					<template>
  <h1>Add/Remove <p> Tag</h1>
  <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  <Transition>
    <p v-if="exists">Hello World!</p>
  </Transition>
</template>

<script>
export default {
  data() {
    return {
      exists: false
    }
  },
  computed: {
    btnText() {
      if(this.exists) {
        return 'Remove';
      }
      else {
        return 'Add';
      }
    }
  }
}
</script>

<style>
  .v-enter-active {
    background-color: lightgreen;
    animation: added 1s;
  }
  .v-leave-active {
    background-color: lightcoral;
    animation: added 1s reverse;
  }
  @keyframes added {
    from {
      opacity: 0;
      translate: -100px 0;
    }
    to {
      opacity: 1;
      translate: 0 0;
    }
  }
  p {
    display: inline-block;
    padding: 10px;
    border: dashed black 1px;
  }
</style>
				
			

The Transition 'name' Prop

You will need distinct names for the <Transition> components in order to distinguish them if you have many <Transition> components and you would want at least one of them to have a different animation.

To specify alternative CSS animation rules for a particular <Transition> component, we can select the name prop for the component, which also modifies the names of the transition classes.

				
					<Transition name="swirl">
				
			

The automatically available classes will now begin with’swirl-‘ rather than ‘v-‘ if the transition name prop value is set to’swirl’:

  1. swirl-enter-from
  2. swirl-enter-active
  3. swirl-enter-to
  4. swirl-leave-from
  5. swirl-leave-active
  6. swirl-leave-to

The <Transition> components in the example below have different animations applied to them using the name prop. As a result, animations for one <Transition> component are applied using the automatically produced CSS classes that begin with ‘v-‘. We rename the other <Transition> component to’swirl’ so that the automatically generated CSS classes beginning with’swirl-‘ can be used to give it animation rules.

Example

App.vue:

				
					<template>
  <h1>Add/Remove <p> Tag</h1>
  <p>The second transition in this example has the name prop "swirl", so that we can keep the transitions apart with different class names.</p>
  <hr>
  <button @click="this.p1Exists = !this.p1Exists">{{btn1Text}}</button><br>
  <Transition>
    <p v-if="p1Exists" id="p1">Hello World!</p>
  </Transition>
  <hr>
  <button @click="this.p2Exists = !this.p2Exists">{{btn2Text}}</button><br>
  <Transition name="swirl">
    <p v-if="p2Exists" id="p2">Hello World!</p>
  </Transition>
</template>

<script>
export default {
  data() {
    return {
      p1Exists: false,
      p2Exists: false
    }
  },
  computed: {
    btn1Text() {
      if(this.p1Exists) {
        return 'Remove';
      }
      else {
        return 'Add';
      }
    },
    btn2Text() {
      if(this.p2Exists) {
        return 'Remove';
      }
      else {
        return 'Add';
      }
    }
  }
}
</script>

<style>
  .v-enter-active {
    background-color: lightgreen;
    animation: added 1s;
  }
  .v-leave-active {
    background-color: lightcoral;
    animation: added 1s reverse;
  }
  @keyframes added {
    from {
      opacity: 0;
      translate: -100px 0;
    }
    to {
      opacity: 1;
      translate: 0 0;
    }
  }
  .swirl-enter-active {
    animation: swirlAdded 1s;
  }
  .swirl-leave-active {
    animation: swirlAdded 1s reverse;
  }
  @keyframes swirlAdded {
    from {
      opacity: 0;
      rotate: 0;
      scale: 0.1;
    }
    to {
      opacity: 1;
      rotate: 360deg;
      scale: 1;
    }
  }
  #p1, #p2 {
    display: inline-block;
    padding: 10px;
    border: dashed black 1px;
  }
  #p2 {
    background-color: lightcoral;
  }
</style>
				
			

JavaScript Transition Hooks

As was previously discussed, each Transition class correlates to an event that we may use to perform JavaScript code.

Transition ClassJavaScript Event
v-enter-frombefore-enter
v-enter-activeenter
v-enter-toafter-enter
enter-cancelled
v-leave-frombefore-leave
v-leave-activeleave
v-leave-toafter-leave
leave-cancelled (v-show only)

In the example below, a method is called that shows a red <div> element when the after-enter event occurs.

Example

App.vue:

				
					<template>
  <h1>JavaScript Transition Hooks</h1>
  <p>This code hooks into "after-enter" so that after the initial animation is done, a method runs that displays a red div.</p>
  <button @click="pVisible=true">Create p-tag!</button><br>
  <Transition @after-enter="onAfterEnter">
    <p v-show="pVisible" id="p1">Hello World!</p>
  </Transition>
  <br>
  <div v-show="divVisible">This appears after the "enter-active" phase of the transition.</div>
</template>

<script>
export default {
  data() {
    return {
      pVisible: false,
      divVisible: false
    }
  },
  methods: {
    onAfterEnter() {
      this.divVisible = true;
    }
  }
}
</script>

<style scoped>
  .v-enter-active {
    animation: swirlAdded 1s;
  }
  @keyframes swirlAdded {
    from {
      opacity: 0;
      rotate: 0;
      scale: 0.1;
    }
    to {
      opacity: 1;
      rotate: 360deg;
      scale: 1;
    }
  }
  #p1, div {
    display: inline-block;
    padding: 10px;
    border: dashed black 1px;
  }
  #p1 {
    background-color: lightgreen;
  }
  div {
    background-color: lightcoral;
  }
</style>
				
			

In the example below, you may utilize the “Toggle” button to stop the <p> element’s enter transition phase, causing the enter-cancelled event to be triggered:

Example

App.vue:

				
					<template>
  <h1>The 'enter-cancelled' Event</h1>
  <p>Click the toggle button again before the enter animation is finished to trigger the 'enter-cancelled' event.</p>
  <button @click="pVisible=!pVisible">Toggle</button><br>
  <Transition @enter-cancelled="onEnterCancelled">
    <p v-if="pVisible" id="p1">Hello World!</p>
  </Transition>
  <br>
  <div v-if="divVisible">You interrupted the "enter-active" transition.</div>
</template>

<script>
export default {
  data() {
    return {
      pVisible: false,
      divVisible: false
    }
  },
  methods: {
    onEnterCancelled() {
      this.divVisible = true;
    }
  }
}
</script>

<style scoped>
  .v-enter-active {
    animation: swirlAdded 2s;
  }
  @keyframes swirlAdded {
    from {
      opacity: 0;
      rotate: 0;
      scale: 0.1;
    }
    to {
      opacity: 1;
      rotate: 720deg;
      scale: 1;
    }
  }
  #p1, div {
    display: inline-block;
    padding: 10px;
    border: dashed black 1px;
  }
  #p1 {
    background-color: lightgreen;
  }
  div {
    background-color: lightcoral;
  }
</style>
				
			

The 'appear' Prop

Use the appear prop on the <Transition> component if you have an element you want to animate when the page loads.

				
					<Transition appear>
  ...
</Transition>
				
			

In this instance, upon initial page load, the appear prop initiates an animation:

Example

App.vue:

				
					<template>
  <h1>The 'appear' Prop</h1>
  <p>The 'appear' prop starts the animation when the p tag below is rendered for the first time as the page opens. Without the 'appear' prop, this example would have had no animation.</p>
  <Transition appear>
    <p id="p1">Hello World!</p>
  </Transition>
</template>

<style>
  .v-enter-active {
    animation: swirlAdded 1s;
  }
  @keyframes swirlAdded {
    from {
      opacity: 0;
      rotate: 0;
      scale: 0.1;
    }
    to {
      opacity: 1;
      rotate: 360deg;
      scale: 1;
    }
  }
  #p1 {
    display: inline-block;
    padding: 10px;
    border: dashed black 1px;
    background-color: lightgreen;
  }
</style>

				
			

Transition Between Elements

If we use <v-if> and <v-else-if> to ensure that only one element is displayed at a time, we can also use the <Transition> component to switch between multiple elements:

Example

App.vue:

				
					<template>
  <h1>Transition Between Elements</h1>
  <p>Click the button to get a new image.</p>
  <p>The new image is added before the previous is removed. We will fix this in the next example with mode="out-in".</p>
  <button @click="newImg">Next image</button><br>
  <Transition>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-if="imgActive === &#039;pizza&#039;" alt="Vue Animations -" title="Vue Animations 1" data-lazy-src="/img_pizza.svg"><noscript><img decoding="async" src="/img_pizza.svg" v-if="imgActive === &#039;pizza&#039;" alt="Vue Animations -" title="Vue Animations 1"></noscript>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-else-if="imgActive === &#039;apple&#039;" alt="Vue Animations -" title="Vue Animations 2" data-lazy-src="/img_apple.svg"><noscript><img decoding="async" src="/img_apple.svg" v-else-if="imgActive === &#039;apple&#039;" alt="Vue Animations -" title="Vue Animations 2"></noscript>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-else-if="imgActive === &#039;cake&#039;" alt="Vue Animations -" title="Vue Animations 3" data-lazy-src="/img_cake.svg"><noscript><img decoding="async" src="/img_cake.svg" v-else-if="imgActive === &#039;cake&#039;" alt="Vue Animations -" title="Vue Animations 3"></noscript>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-else-if="imgActive === &#039;fish&#039;" alt="Vue Animations -" title="Vue Animations 4" data-lazy-src="/img_fish.svg"><noscript><img decoding="async" src="/img_fish.svg" v-else-if="imgActive === &#039;fish&#039;" alt="Vue Animations -" title="Vue Animations 4"></noscript>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-else-if="imgActive === &#039;rice&#039;" alt="Vue Animations -" title="Vue Animations 5" data-lazy-src="/img_rice.svg"><noscript><img decoding="async" src="/img_rice.svg" v-else-if="imgActive === &#039;rice&#039;" alt="Vue Animations -" title="Vue Animations 5"></noscript>
  </Transition>
</template>

<script>
export default {
  data() {
    return {
      imgActive: 'pizza',
      imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'],
      indexNbr: 0
    }
  },
  methods: {
    newImg() {
      this.indexNbr++;
      if(this.indexNbr >= this.imgs.length) {
        this.indexNbr = 0;
      }
      this.imgActive = this.imgs[this.indexNbr];
    }
  }
}
</script>

<style>
  .v-enter-active {
    animation: swirlAdded 1s;
  }
  .v-leave-active {
    animation: swirlAdded 1s reverse;
  }
  @keyframes swirlAdded {
    from {
      opacity: 0;
      rotate: 0;
      scale: 0.1;
    }
    to {
      opacity: 1;
      rotate: 360deg;
      scale: 1;
    }
  }
  img {
    width: 100px;
    margin: 20px;
  }
  img:hover {
    cursor: pointer;
  }
</style>
				
			

mode="out-in"

The following image is added in the example above before the preceding image is deleted.

To ensure that an element is removed before another is introduced, we utilize the mode=”out-in” prop and prop value on the <Transition> component.

Example

Apart from the mode=”out-in” setting, the ‘imgActive‘ computed value is also utilized in this example instead of the ‘newImg‘ method from the previous example.

App.vue:

				
					<template>
  <h1>mode="out-in"</h1>
  <p>Click the button to get a new image.</p>
  <p>With mode="out-in", the next image is not added until the current image is removed. Another difference from the previous example, is that here we use computed prop instead of a method.</p>
  <button @click="indexNbr++">Next image</button><br>
  <Transition mode="out-in">
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-if="imgActive === &#039;pizza&#039;" alt="Vue Animations -" title="Vue Animations 1" data-lazy-src="/img_pizza.svg"><noscript><img decoding="async" src="/img_pizza.svg" v-if="imgActive === &#039;pizza&#039;" alt="Vue Animations -" title="Vue Animations 1"></noscript>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-else-if="imgActive === &#039;apple&#039;" alt="Vue Animations -" title="Vue Animations 2" data-lazy-src="/img_apple.svg"><noscript><img decoding="async" src="/img_apple.svg" v-else-if="imgActive === &#039;apple&#039;" alt="Vue Animations -" title="Vue Animations 2"></noscript>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-else-if="imgActive === &#039;cake&#039;" alt="Vue Animations -" title="Vue Animations 3" data-lazy-src="/img_cake.svg"><noscript><img decoding="async" src="/img_cake.svg" v-else-if="imgActive === &#039;cake&#039;" alt="Vue Animations -" title="Vue Animations 3"></noscript>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-else-if="imgActive === &#039;fish&#039;" alt="Vue Animations -" title="Vue Animations 4" data-lazy-src="/img_fish.svg"><noscript><img decoding="async" src="/img_fish.svg" v-else-if="imgActive === &#039;fish&#039;" alt="Vue Animations -" title="Vue Animations 4"></noscript>
    <img decoding="async" src="data:image/svg+xml,%3Csvg%20xmlns='http://www.w3.org/2000/svg'%20viewBox='0%200%200%200'%3E%3C/svg%3E" v-else-if="imgActive === &#039;rice&#039;" alt="Vue Animations -" title="Vue Animations 5" data-lazy-src="/img_rice.svg"><noscript><img decoding="async" src="/img_rice.svg" v-else-if="imgActive === &#039;rice&#039;" alt="Vue Animations -" title="Vue Animations 5"></noscript>
  </Transition>
</template>

<script>
export default {
  data() {
    return {
      imgs: ['pizza', 'apple', 'cake', 'fish', 'rice'],
      indexNbr: 0
    }
  },
  computed: {
    imgActive() {
      if(this.indexNbr >= this.imgs.length) {
        this.indexNbr = 0;
      }
      return this.imgs[this.indexNbr];
    }
  }
}
</script>

<style>
  .v-enter-active {
    animation: swirlAdded 0.7s;
  }
  .v-leave-active {
    animation: swirlAdded 0.7s reverse;
  }
  @keyframes swirlAdded {
    from {
      opacity: 0;
      rotate: 0;
      scale: 0.1;
    }
    to {
      opacity: 1;
      rotate: 360deg;
      scale: 1;
    }
  }
  img {
    width: 100px;
    margin: 20px;
  }
  img:hover {
    cursor: pointer;
  }
</style>
				
			

Transition with Dynamic Components

Additionally, we may animation transitioning between dynamic components by using the <Transition> component:

Example

App.vue:

				
					<template>
  <h1>Transition with Dynamic Components</h1>
  <p>The Transition component wraps around the dynamic component so that the switching can be animated.</p>
  <button @click="toggleValue = !toggleValue">Switch component</button>
  <Transition mode="out-in">
    <component :is="activeComp"></component>
  </Transition>
</template>

<script>
  export default {
    data () {
      return {
        toggleValue: true
      }
    },
    computed: {
      activeComp() {
        if(this.toggleValue) {
          return 'comp-one'
        }
        else {
          return 'comp-two'
        }
      }
    }
  }
</script>

<style>
  .v-enter-active {
    animation: slideIn 0.5s;
  }
  @keyframes slideIn {
    from {
      translate: -200px 0;
      opacity: 0;
    }
    to {
      translate: 0 0;
      opacity: 1;
    }
  }
  .v-leave-active {
    animation: slideOut 0.5s;
  }
  @keyframes slideOut {
    from {
      translate: 0 0;
      opacity: 1;
    }
    to {
      translate: 200px 0;
      opacity: 0;
    }
  }
  #app {
    width: 350px;
    margin: 10px;
  }
  #app > div {
    border: solid black 2px;
    padding: 10px;
    margin-top: 10px;
  }
</style>
				
			
Share this Doc

Vue Animations

Or copy link

Explore Topic