w3resource
Vue Tutorial

Enter/Leave & List Transitions

Overview

Vue.js provides a variety of ways to apply transitions when items are inserted, updated, or removed from the DOM. These ways include tools to:

  • apply classes for CSS transitions and animations automatically
  • integrate 3rd-party CSS animation libraries, for example Animate.css
  • use JavaScript to manipulate the DOM directly during transition hooks
  • integrate 3rd-party JavaScript animation libraries, for exampleVelocity.js

Transitioning Single Elements/Components

Vue provides us with a transition wrapper component, this allows us to add entering/leaving transitions for any component or element in the following context:

  • Conditional rendering using v-if
  • Dynamic components
  • Component root nodes
  • Conditional display using v-show>

An example is shown below:

HTML

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>

JS

new Vue({
  el: '#demo',
  data: {
    show: true
  }
})

CSS

.fade-enter-active, .fade-leave-active {
  transition: opacity .5s;
}
.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

Transition Classes

Six classed can be applied for enter/leave transition.

  1. v-enter: the starting state for enter.
  2. v-enter-active: the active state for enter.
  3. v-enter-to: the ending state for enter.
  4. v-leave: the starting state for leave.
  5. v-leave-active: the active state for leave.
  6. v-leave-to: the ending state for leave.

All these classes will be prefixed with the name of the transition. The v- prefix is the default prefix when we use a <transition> element with no name. but if we had <transition name="my-transition"> then we can change v-enter to my-transition-enter.

CSS Transitions

the most common transition types use CSS transitions:

<div id="example-1">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition name="slide-fade">
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#example-1',
  data: {
    show: true
  }
})

CSS

.slide-fade-enter-active {
  transition: all .3s ease;
}
.slide-fade-leave-active {
  transition: all .8s cubic-bezier(1.0, 0.5, 0.8, 1.0);
}
transform: translateX(10px);
  opacity: 0;
}

CSS Animations

We can apply CSS animations much the same way we apply CSS transitions, the difference is that v-enter is not removed immediately after the element is inserted, rather on animationed event:

HTML

<div id="example-2">
  <button @click="show = !show">Toggle show</button>
  <transition name="bounce">
    <p v-if="show"> some sample Lorem ipsum</p>
  </transition>
</div>

JS

new Vue({
  el: '#example-2',
  data: {
    show: true
  }
})

CSS

.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

Custom Transition Classes

We can specify custom transition classes to override the conventional class names by providing the following attributes:

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

this is important when we wish to use an external CSS library together with Vue's transition system.

Using Transitions and Animations Together

For this to happen we will need to attach event listeners to know when a transition has ended. It can be either transitioned or animationed, this depends on the type of CSS rules applied.

However, when we need both them on the same element, we will have to explicitly declare the type we want Vue to care about in a type attribute, with a value of either transition or animation.

Explicit Transition Durations

By default, Vue waits for the first animationed or transitioned event on the root transition element. But there are times when this is not desired, insuch case we can specify an explicit transition duration (in milliseconds) using the duration prop on the <transition> component.

Additionally, we can specify separate values for enter and leave durations:

<transition :duration="{ enter: 500, leave: 800 }">...</transition>

JavaScript Hooks

we can define JavaScript hooks in attributes as well:

HTML

<transition
  v-on:before-enter="beforeEnter"
  v-on:enter="enter"
  v-on:after-enter="afterEnter"
  v-on:enter-cancelled="enterCancelled"

  v-on:before-leave="beforeLeave"
  v-on:leave="leave"
  v-on:after-leave="afterLeave"
  v-on:leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>

JS

methods: {
  // --------
  // ENTERING
  // --------

  beforeEnter: function (el) {
    // ...
  },
  enter: function (el, done) {
    // ...
    done()
  },
  afterEnter: function (el) {
    // ...
  },
  enterCancelled: function (el) {
    // ...
  },

  // --------
  // LEAVING
  // --------

  beforeLeave: function (el) {
    // ...
  },
  leave: function (el, done) {
    // ...
    done()
  },
  afterLeave: function (el) {
    // ...
  },
  leaveCancelled: function (el) {
    // ...
  }
}

The hooks above can be used on their own or in combination with CSS transitions/animations

It is recommended that we explicitely add v-bind:css="false" for JavaScript-only transitions so that Vue can skip the CSS detection. Thus preventing CSS rules from accidentally interfering with the transition. An example using Velocity.js is shown below:

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>

<div id="example-4">
  <button @click="show = !show">
    Toggle
  </button>
  <transition
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
    v-bind:css="false"
  >
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>

JS

new Vue({
  el: '#example-4',
  data: {
    show: false
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
    },
    enter: function (el, done) {
      Velocity(el, { opacity: 1, fontSize: '1.4em' }, { duration: 300 })
      Velocity(el, { fontSize: '1em' }, { complete: done })
    },
    leave: function (el, done) {
      Velocity(el, { translateX: '15px', rotateZ: '50deg' }, { duration: 600 })
      Velocity(el, { rotateZ: '100deg' }, { loop: 2 })
      Velocity(el, {
        rotateZ: '45deg',
        translateY: '30px',
        translateX: '30px',
        opacity: 0
      }, { complete: done })
    }
  }
})

Transitions on Intial Render

if we want to apply a transition on the initial render of a node, we can add the appear attribute like so:

<transition appear>
  <!-- ... -->
</transition>
This will use the transition specified for entering and leaving by default. But you can specify custom CSS classes if you want:
<transition
  appear
  appear-class="custom-appear-class"
  appear-to-class="custom-appear-to-class" (2.1.8+)
  appear-active-class="custom-appear-active-class"
>
  <!-- ... -->
</transition>

As well as custom JavaScript hooks:

<transition
  appear
  v-on:before-appear="customBeforeAppearHook"
  v-on:appear="customAppearHook"
  v-on:after-appear="customAfterAppearHook"
  v-on:appear-cancelled="customAppearCancelledHook"
>
  <!-- ... -->
</transition>

Transition Between Elements

We can also transition between raw elements using v-if/v-else. One of the most common two-element transitions that exists is between a list container and a message describing an empty list:

<transition>
  <table v-if="items.length > 0">
    <!-- ... -->
  </table>
  <p v-else>Sorry, no item was found.</p>
</transition>

Though this works well, but there is one caveat we should be aware of which is:

When we are toggling between elements that have the same tag name, we must tell Vue that they are distinct elements by giving them unique key attributes. Else, Vue's compiler will only replace the content of the element for efficiency.

Take for instance:

<transition>
  <button v-if="isEditing" key="save">
    Save
  </button>
  <button v-else key="edit">
    Edit
  </button>
</transition>

We can use the v-bind directive instead of using v-if and v-else,:

<transition>
  <button v-bind:key="isEditing">
    {{ isEditing ? 'Save' : 'Edit' }}
  </button>
</transition>

It is also possible for us to transition between any number of elements, whether we use v-if or v-bind a single element to a dynamic property.

HTML

<transition>
  <button v-bind:key="docState">
    {{ buttonMessage }}
  </button>
</transition>

JS

// ...
```computed: {
  buttonMessage: function () {
    switch (this.docState) {
      case 'saved': return 'Edit'
      case 'edited': return 'Save'
      case 'editing': return 'Cancel'
    }
  }
}

Transition Modes

Entering and leaving transitions simultaneously are not always desirable, hence Vue offers some alternative transition modes:

  • in-out: New elements transition in first, then when it completes, the current element will transition out.
  • out-in: Current elements transition out first, then when it completes, the new element will transition in.

Transitioning Between Components

Transitioning between components is simpler, we don't the key attribute, rather, we will wrap a dynamic component:

HTML

<transition name="component-fade" mode="out-in">
  <component v-bind:is="view"></component>
</transition>

JS

new Vue({
  el: '#transition-components-demo',
  data: {
    view: 'v-a'
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
})

CSS

.component-fade-enter-active, .component-fade-leave-active {
  transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active below version 2.1.8 */ {
  opacity: 0;
}

List Transitions

Thus far, we have managed transitions for:

  • Individual nodes
  • Multiple nodes where only one node is rendered at a time.
  • So what about we have a whole list of items to simultaneously render, then we will use the <transition-group> component. There are few things that are important to know about this component before we dive into examples.

  • Unlike <transition>, this renders an actual element: a <span> by default. You can change the element that is rendered with the tag attribute.
  • Transition modes aren't available, because we are not alternating between mutually exclusive elements anymore.
  • Elements inside as a rule should have a unique key attribute.
  • CSS transition classes will be applied to inner elements and not to the groups/containers themselves.

List Entering/Leaving Transitions

Here is an example for transitioning entering and leaving using the CSS classes we hav used previously:

HTML

<div id="list-demo">
  <button v-on:click="add">Add</button>
  <button v-on:click="remove">Remove</button>
  <transition-group name="list" tag="p">
    <span v-for="item in items" v-bind:key="item" class="list-item">
      {{ item }}
    </span>
  </transition-group>
</div>

JS

new Vue({
  el: '#list-demo',
  data: {
    items: [1,2,3,4,5,6,7,8,9],
    nextNum: 10
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
  }
})

CSS

.list-item {
  display: inline-block;
  margin-right: 10px;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to /* .list-leave-active below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(30px);
}

In the example above, when we add or remove an item, the ones that are around it instantly snap into their new place instead of smoothly transitioning.

List Move Transitions

The <transition-group> not only animate entering and leaving, but it also changes in position. The new concept we need to know to use this feature is the addition of the v-move class.

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>

<div id="flip-list-demo" class="demo">
  <button v-on:click="shuffle">Shuffle</button>
  <transition-group name="flip-list" tag="ul">
    <li v-for="item in items" v-bind:key="item">
      {{ item }}
    </li>
  </transition-group>
</div>

JS

new Vue({
  el: '#flip-list-demo',
  data: {
    items: [1,2,3,4,5,6,7,8,9]
  },
  methods: {
    shuffle: function () {
      this.items = _.shuffle(this.items)
    }
  }
})

CSS

.flip-list-move {
  transition: transform 1s;
}

The code above can be used together with the former code example for add/remove numbers.

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js"></script>

<div id="list-complete-demo" class="demo">
  <button v-on:click="shuffle">Shuffle</button>
  <button v-on:click="add">Add</button>
  <button v-on:click="remove">Remove</button>
  <transition-group name="list-complete" tag="p">
    <span
      v-for="item in items"
      v-bind:key="item"
      class="list-complete-item"
    >
      {{ item }}
    </span>
  </transition-group>
</div>

JS

new Vue({
  el: '#list-complete-demo',
  data: {
    items: [1,2,3,4,5,6,7,8,9],
    nextNum: 10
  },
  methods: {
    randomIndex: function () {
      return Math.floor(Math.random() * this.items.length)
    },
    add: function () {
      this.items.splice(this.randomIndex(), 0, this.nextNum++)
    },
    remove: function () {
      this.items.splice(this.randomIndex(), 1)
    },
    shuffle: function () {
      this.items = _.shuffle(this.items)
    }
  }
})

CSS

.list-complete-item {
  transition: all 1s;
  display: inline-block;
  margin-right: 10px;
}
.list-complete-enter, .list-complete-leave-to
/* .list-complete-leave-active below version 2.1.8 */ {
  opacity: 0;
  transform: translateY(30px);
}
.list-complete-leave-active {
  position: absolute;
}

Staggering List Transitions

It is also possible to stagger transition in a list by communicating with JavaScript transition through data attributes:

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
<div id="staggered-list-demo">
  <input v-model="query">
  <transition-group
    name="staggered-fade"
    tag="ul"
    v-bind:css="false"
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
  >
    <li
      v-for="(item, index) in computedList"
      v-bind:key="item.msg"
      v-bind:data-index="index"
    >{{ item.msg }}</li>
  </transition-group>
</div>

JS

new Vue({
  el: '#staggered-list-demo',
  data: {
    query: '',
    list: [
      { msg: 'Bruce Lee' },
      { msg: 'Jackie Chan' },
      { msg: 'Chuck Norris' },
      { msg: 'Jet Li' },
      { msg: 'Kung Fury' }
    ]
  },
  computed: {
    computedList: function () {
      var vm = this
      return this.list.filter(function (item) {
        return item.msg.toLowerCase().indexOf(vm.query.toLowerCase()) !== -1
      })
    }
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
      el.style.height = 0
    },
    enter: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 1, height: '1.6em' },
          { complete: done }
        )
      }, delay)
    },
    leave: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 0, height: 0 },
          { complete: done }
        )
      }, delay)
    }
  }
})

Reusable Transitions

We can reuse transitions through Vue's component system. To do this we a <transition> or <transition-group> at the root, then pass any children into the transition component.

Vue.component('my-special-transition', {
  template: '\
    <transition\
      name="very-special-transition"\
      mode="out-in"\
      v-on:before-enter="beforeEnter"\
      v-on:after-enter="afterEnter"\
    >\
      <slot></slot>\
    </transition>\
  ',
  methods: {
    beforeEnter: function (el) {
      // ...
    },
    afterEnter: function (el) {
      // ...
    }
  }
})

Functional components are well suited to this task:

Vue.component('my-special-transition', {
  functional: true,
  render: function (createElement, context) {
    var data = {
      props: {
        name: 'very-special-transition',
        mode: 'out-in'
      },
      on: {
        beforeEnter: function (el) {
          // ...
        },
        afterEnter: function (el) {
          // ...
        }
      }
    }
    return createElement('transition', data, context.children)
  }
})

Dynamic Transitions

Transitions in Vue are also data-driven. The most basic example of a dynamic transition will bind the name attribute to a dynamic property.

<transition v-bind:name="transitionName">
  <!-- ... -->
</transition>

Then we define the CSS transitions/animations using Vue's transition class conventions and want to switch between them. Any transition attribute can be dynamically bound.

However, the ultimate way for creating dynamic transitions is by using components that accept props to change the nature of the transition(s) to be used.