Vue <Transition> Component


Example

Using the built-in <Transition> component to animate a <p> element as it is removed with v-if:

<Transition>
  <p v-if="exists">Hello World!</p>
</Transition>
Run Example »

See more examples below.


Definition and Usage

The built-in <Transition> component is used to animate elements as they are added or removed with v-if, v-show, or with dynamic components.

The rules for how elements are animated is written inside automatically generated classes or JavaScript transition hooks. See tables below.

There can only be one element at the root level of the <Transition> component.


Props

Prop Description
none Default. Run Example »
appear If set to true, the element is also animated as it is mounted for the first time. Default value is false. Run Example »
mode mode="out-in" makes sure that the initial element leaves before the next element enters. mode="in-out" makes sure that the new element enters before the old element leaves. Default is that the old element leaves at the same time as the new element enters. Run Example »
name Specify the name of a transition. If we have more than one transition we need to give them unique names to tell them apart. name="swirl" makes sure the CSS transition classes start with swirl- instead of the default prefix v-. Run Example »
css Boolean. :css="false" tells the Vue compiler that no transition classes are used for this transition, only JavaScript hooks. With this prop set, the done() callback must be used inside the enter and leave hooks. Run Example »
type Specify whether to wait for 'animation' or 'transition' to finish a transition. If both a CSS animation and a CSS transition is set, and this type prop is not set, Vue will detect the longest duration of these two and use that as the transition time.
duration Specify the length of the transition time for 'enter' and 'leave'. Default is to end when the CSS animation or CSS transition ends. Specific times can be defined like this :duration="{enter:2000, leave:1000 }", or like this duration="1000".
enterFromClass
enterActiveClass
enterToClass
appearFromClass
appearActiveClass
appearToClass
leaveFromClass
leaveActiveClass
leaveToClass
Use these props to rename transition classes.

Using one of these props like this enter-active-class="entering" means that this transition class can be referred to as .entering in CSS, instead of the default .v-enter-active. The convention is to use kebab-case for props in template to be in line with how attributes are written in HTML.

Run Example »

CSS Transition Classes

When we use the <Transition> component, we automatically get six different CSS classes we can use to animate elements when they are added or removed.

These classes are active at different stages when elements are added (enter) or removed (leave):

Transition Class Description
v-enter-from Initial style of the element when the entering phase starts Run Example »
v-enter-active The element's style during the entering phase Run Example »
v-enter-to The element's style right at the end of the entering phase Run Example »
v-leave-from Initial style of the element when the leaving phase starts Run Example »
v-leave-active The element's style during the leaving phase Run Example »
v-leave-to The element's style right at the end of the leaving phase Run Example »

JavaScript Transition Hooks

The transition classes above corresponds to events that we can hook into to run JavaScript code.

JavaScript Event Description
before-enter Called right at the start of the enter phase
enter Called after the 'before-enter' hook, during the enter phase Run Example »
after-enter Called right at the end of the enter transition Run Example »
enter-cancelled Called if the enter transition is cancelled Run Example »
before-leave Called right at the start of the leave phase Run Example »
leave Called after the 'before-leave' hook, during the leave phase Run Example »
after-leave Called right at the end of the leave transition
leave-cancelled This is only called if v-show is used and the leave phase is cancelled

More examples

Example 1

A <p> element slides in and out when it is toggled.

<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>
Run Example »

Example 2

A <p> element has separate background colors during 'enter' and 'leave'

<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>
Run Example »

Example 3

<p> elements are animated differently, using the name prop to differentiate the <Transition> components.

<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>
Run Example »

Example 4

The after-enter event triggers a <div> element to be displayed.

<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>
  .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>
Run Example »

Example 5

A toggle button triggers the enter-cancelled event.

<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>
  .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>
Run Example »

Example 6

The appear prop starts the <p> element animation right after the page is loaded.

<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>
Run Example »

Example 7

Flip through images with animation on 'enter' and 'leave'. A new image is added before the old image is removed.

<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 src="/img_pizza.svg" v-if="imgActive === 'pizza'">
    <img src="/img_apple.svg" v-else-if="imgActive === 'apple'">
    <img src="/img_cake.svg" v-else-if="imgActive === 'cake'">
    <img src="/img_fish.svg" v-else-if="imgActive === 'fish'">
    <img src="/img_rice.svg" v-else-if="imgActive === 'rice'">
  </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>
Run Example »

Example 8

Flip through images with animation on 'enter' and 'leave'. The mode="out-in" prevents new images to be added until the old image is removed.

<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 src="/img_pizza.svg" v-if="imgActive === 'pizza'">
    <img src="/img_apple.svg" v-else-if="imgActive === 'apple'">
    <img src="/img_cake.svg" v-else-if="imgActive === 'cake'">
    <img src="/img_fish.svg" v-else-if="imgActive === 'fish'">
    <img src="/img_rice.svg" v-else-if="imgActive === 'rice'">
  </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>
Run Example »

Example 9

Switching between components is animated.

<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>
Run Example »

Example 10

Switching between components is animated.

<template>
  <h1>The :css="false" Prop</h1>
  <p>With the 'css' prop set to 'false', we tell the compiler that JavaScript hooks are used instead of CSS transition classes.</p>
  <p>When we use :css="false", we must call done() inside the 'enter' and the 'leave' hooks, to tell the browser when those transitions are finished.</p>
  <button @click="pVisible=!pVisible">Toggle</button>
  <div>
    <Transition
      :css="false" 
      @enter="onEnter"
      @after-enter="onAfterEnter"
      @before-leave="onBeforeLeave"
      @leave="onLeave"
    >
      <p 
        v-if="pVisible"
        id="p1">
        Hello World!
      </p>
    </Transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      pVisible: false
    }
  },
  methods: {
    onEnter(el,done) {
      let pos = 0;
      window.requestAnimationFrame(frame);
      function frame() {
        if (pos > 150) {
          done();
        } else {
          pos++; 
          el.style.left = pos + "px"; 
          window.requestAnimationFrame(frame);
        }
      }
    },
    onAfterEnter(el) {
      el.style.backgroundColor = "yellow";
    },
    onBeforeLeave(el) {
      el.style.backgroundColor = "lightgreen";
    },
    onLeave(el,done) {
      let pos = 150;
      window.requestAnimationFrame(frame);
      function frame() {
        if (pos < 0) {
          done();
        }
        else {
          pos--;
          el.style.left = pos + "px"; 
          window.requestAnimationFrame(frame);
        }
      }
    }
  }
}
</script>

<style>
  #p1 {
    position: absolute;
    padding: 10px;
    border: dashed black 1px;
    background-color: lightgreen;
  }
  #app > div {
    position: relative;
    background-color: coral;
    width: 300px;
    height: 300px;
    border: dashed black 1px;
    margin-top: 20px;
  }
</style>
Run Example »

Example 11

Using the enterActiveClass prop to rename the 'v-enter-active' CSS class to 'entering'.

<template>
  <h1>The 'enterActiveClass' Prop</h1>
  <button @click="this.exists = !this.exists">{{btnText}}</button><br>
  <Transition enter-active-class="entering">
    <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>
  .entering {
    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>
Run Example »

Related Pages

Vue Tutorial: Vue Animations

Vue Tutorial: Vue Animations with v-for

Vue Reference: Vue <TransitionGroup> Component


Copyright 1999-2023 by Refsnes Data. All Rights Reserved.