Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Vue.js 2.0 transition effect


May 07, 2021 Vue.js 2.0


Table of contents


Vue.js 2.0 Transition Effect Overview

Vue provides a variety of different ways to make app transitions when inserting, updating, or removing DOMs. Includes the following tools:

  • Class is automatically applied in CSS transitions and animations
  • You can work with third-party CSS animation libraries, such as Animate .css
  • Use JavaScript to manipulate the DOM directly in the transition hook function
  • You can work with third-party JavaScript animation libraries, such as Velocity .js

Here, we'll just talk about transitions to entry, departure, and lists, and you can also look at the "Managing Transition State" in the next section.

Transition of a single element/component

Vue provides the encapsulation components for transition, and in the following cases, you can add an enter/lefting transition to any element and component

  • Conditional rendering (using v-if)
  • Conditional presentation (using v-show)
  • Dynamic components
  • The component root node

Here is a typical example:

<div id="demo">
  <button v-on:click="show = !show">
    Toggle
  </button>
  <transition name="fade">
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#demo',
  data: {
    show: true
  }
})
.fade-enter-active, .fade-leave-active {
  transition: opacity .5s
}
.fade-enter, .fade-leave-active {
  opacity: 0
}

hello

After the element is encapsulated as a staging component, Vue will be inserted or deleted

  1. Automatically sniffs the target element for CSS transitions or animations and adds/removes CSS class names when appropriate.
  2. If the staging component sets up the JavaScript hook function for the transition, the hook function is called at the appropriate stage.
  3. If the JavaScript hook is not found and the CSS transition/animation is not detected, the DOM operation (insert/delete) is performed immediately in the next frame. (Note: This refers to the browser frame-by-frame animation mechanism, unlike Vue, and Vue's nextTick concept)

Transition-CSS-class name

There are 4 (CSS) class names that switch between enter/leave transitions

  1. v-enter: Defines the starting state of the transition. Takes effect when an element is inserted and is removed at the next frame.
  2. v-enter-active: Defines the end state that enters the transition. Takes effect when an element is inserted and is removed after transition/animation is complete.
  3. v-leave: Defines the starting state of leaving the transition. Takes effect when the departure transition is triggered and is removed at the next frame.
  4. v-leave-active: Defines the end state of leaving the transition. Effective when the departure transition is triggered and removed after transition/animation is complete.

Vue.js 2.0 transition effect

For these class names that switch in the enter/leave transition, v- is the prefix for those class names. You can reset the prefix, such as v-enter, by replacing it with my-transition-enter.

v-enter-active and v-leave-active can control the different stages of the in/out transition, as shown in the following sections.

CSS transition

Common transitions are available using CSS transitions.

Here's a simple example:

<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
  }
})
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.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);
}
.slide-fade-enter, .slide-fade-leave-active {
  padding-left: 10px;
  opacity: 0;
}

hello

CSS animation

CSS animation usage transitions to CSS, with the difference that the v-enter class name in the animation is not deleted immediately after the node inserts the DOM, but when the animationend event is triggered.

Example: (compatibility prefix omitted)

<div id="example-2">
  <button @click="show = !show">Toggle show</button>
  <transition name="bounce">
    <p v-if="show">Look at me!</p>
  </transition>
</div>
new Vue({
  el: '#example-2',
  data: {
    show: true
  }
})
.bounce-enter-active {
  animation: bounce-in .5s;
}
.bounce-leave-active {
  animation: bounce-out .5s;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}
@keyframes bounce-out {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(0);
  }
}

Look at me!

Custom transition class name

We can customize the transition class name with the following features:

  • enter-class
  • enter-active-class
  • leave-class
  • leave-active-class

Their priority is higher than normal class names, which is useful for Vue's transition system in conjunction with other third-party CSS animation libraries .css such as Animate.

Example:

<link href="https://unpkg.com/[email protected]/animate.min.css" rel="external nofollow" target="_blank"  rel="stylesheet" type="text/css">
<div id="example-3">
  <button @click="show = !show">
    Toggle render
  </button>
  <transition
    name="custom-classes-transition"
    enter-active-class="animated tada"
    leave-active-class="animated bounceOutRight"
  >
    <p v-if="show">hello</p>
  </transition>
</div>
new Vue({
  el: '#example-3',
  data: {
    show: true
  }
})

hello

Use Transitions and Animations at the same time

In order for Vue to know that the transition is complete, the appropriate event listener must be set up. I t can be transitionend or animationend, depending on the CSS rules applied to the element. If you use 1 of these, Vue automatically identifies the type and sets the listening.

However, in some scenarios, you need to set two transition dynamics for the same element at the same time, such as animation being triggered and completed quickly, and the transition effect is not over yet. In this case, you need to use the type attribute and set animation or transition to explicitly state the type of Vue listening you need.

JavaScript hooks

JavaScript hooks can be declared in properties

<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>

These hook functions can be used in conjunction with CSS transitions/animations or they can be used separately.

In enter and leave, the callback function done is a must when only JavaScript transitions are used. Otherwise, they are called synchronously and the transition is completed immediately.

It is recommended to add v-bind:css="false" for elements that use only JavaScript transitions, and Vue skips detection of CSS. This also avoids the impact of CSS during the transition.

A simple example of .js Velocity:

<!--
Velocity works very much like jQuery.animate and is
a great option for JavaScript animations
-->
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" ></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>
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 })
    }
  }
})

The transition to the initial rendering

You can set the transition of the node in the initial rendering through the see attribute:

<transition appear>
  <!-- ... -->
</transition>

The CSS class name can also be customized here by default, as is the entry and departure transition.

<transition
  appear
  appear-class="custom-appear-class"
  appear-active-class="custom-appear-active-class"
>
  <!-- ... -->
</transition>

Custom JavaScript hooks:

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

The transition of multiple elements

We'll discuss "Transitions for Multiple Components" later in this chapter, where you can use v-if/v-else for native labels. The most common multi-label transition is a list and an element that describes an empty message for this list:

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

This can be used, but there is one thing to note:

When elements with the same label name switch, you need to set unique values through the key attribute to mark them to allow Vue to distinguish them, otherwise Vue will only replace the contents of the same label for efficiency. E ven if it's not technically necessary, it's a better practice to set key for multiple elements in a component.

Example:

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

In some scenarios, v-if and v-else can also be replaced by setting different states for the key attribute of the same element, and the above example can be override as:

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

Transitions that use multiple elements of v-if can be overrideed as a single element transition bound to dynamic properties. For example:

<transition>
  <button v-if="docState === 'saved'" key="saved">
    Edit
  </button>
  <button v-if="docState === 'edited'" key="edited">
    Save
  </button>
  <button v-if="docState === 'editing'" key="editing">
    Cancel
  </button>
</transition>

Can be rewritten as:

<transition>
  <button v-bind:key="docState">
    {{ buttonMessage }}
  </button>
</transition>
// ...
computed: {
  buttonMessage: function () {
    switch (docState) {
      case 'saved': return 'Edit'
      case 'edited': return 'Save'
      case 'editing': return 'Cancel'
    }
  }
}

Transition mode

Here's another question, try clicking on the button below:

In the transition between the "on" button and the "off" button, both buttons are redrawn, and one leaves the transition and the other begins to transition. This is the default behavior of slt;transitions - entry and departure occur at the same time.

Works fine when the elements are absolutely positioned above each other:

Then, let's add translate to make them move like sliding transitions:

The transition of entry and departure that takes effect at the same time does not meet all requirements, so Vue provides a transition mode

  • in-out: The new element transitions first, and then the current element transitions away.
  • out-in: The current element transitions first, and then the new element transitions in.

Transition with the switch button before override with out-in:

<transition name="fade" mode="out-in">
  <!-- ... the buttons ... -->
</transition>

Adding a simple feature solves the previous transition problem without any additional code.

In-out mode is not often used, but it is useful for slightly different transition effects. Combine the examples of previous sliding fades with:

Isn't that cool?

Transition of multiple components

The transition between multiple components is much simpler - we don't need to use the key feature. Instead, we only need to use dynamic components:

<transition name="component-fade" mode="out-in">
  <component v-bind:is="view"></component>
</transition>
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>'
    }
  }
})
A B
Component A

List transition

So far, we've talked about transition:

  • A single node
  • Render more than one node at a time

So how do you render the entire list at the same time, such as using v-for? I n this scenario, use the components of the .lt;transition-group. Before we dive into the example, let's look at a few features of this component:

  • Unlike slt;transition, it is rendered as a real element: the default is a slt;span. You can also replace it with another element with the tag attribute.
  • Element Must specify a unique key attribute value

The entry and departure transitions of the list

Now let's drill down from a simple example to enter and leave the transition using the same CSS class name as before.

<div id="list-demo" class="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>
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)
    },
  }
})
.list-item {
  display: inline-block;
  margin-right: 10px;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-active {
  opacity: 0;
  transform: translateY(30px);
}

Results:

123456789

One problem with this example is that when you add and remove elements, the surrounding elements move instantaneously to the position of their new layout, rather than a smooth transition, which we'll fix below.

The displacement transition of the list

There's one special thing about the components. N ot only can you enter and leave the animation, but you can also change the positioning. T o use this new feature, you only need to understand the new v-move feature, which is applied as the element changes its positioning. As with the previous class name, you can customize the prefix with the name property, or you can set it manually with the move-class property.

v-move is useful for setting transition switching times and transition curves, and you'll see the following examples:

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js" rel="external nofollow"  rel="external nofollow" ></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>
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)
    }
  }
})
.flip-list-move {
  transition: transform 1s;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

This looks amazing, with the internal implementation, Vue using a simple animation queue called FLIP to use transforms to smooth the transition of elements from their previous positions to new locations.

We combine the examples we implemented earlier with this technique so that all changes in our list will have an animated transition.

<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.1/lodash.min.js" rel="external nofollow"  rel="external nofollow" ></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>
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)
    }
  }
})
.list-complete-item {
  transition: all 1s;
  display: inline-block;
  margin-right: 10px;
}
.list-complete-enter, .list-complete-leave-active {
  opacity: 0;
  transform: translateY(30px);
}
.list-complete-leave-active {
  position: absolute;
}

Results:

123456789

It is important to note that elements that use the FLIP transition cannot be set to display: inline. As an alternative, you can set it to display: inline-block or place it in flex.

FLIP animations not only enable single-column transitions, but also multi-dimensional mesh transitions

Progressive transition of the list

By communicating with JavaScript through the data property, you can make a gradual transition to the list:

<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" ></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>
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)
    }
  }
})

Results:

  • Bruce Lee
  • Jackie Chan
  • Chuck Norris
  • Jet Li
  • Kung Fury

Reusable transitions

Transitions can be reused through Vue's component system. To create a re-usable staging component, all you need to do is use the .lt;transition> or the .lt;transition-group-gt; as the root component, and then place any sub-components in it.

A simple example of using template:

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) {
      // ...
    }
  }
})

Function components are better suited for 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 transition

Even transitions are data-driven in Vue! The most basic example of a dynamic transition is to bind dynamic values through the name attribute.

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

It is useful to switch between transitions when you want to define CSS transitions/animations with Vue's transition system.

All transition features are dynamically bound. I t is not only a simple feature, through the hook function method of the event, you can get the appropriate context data. This means that different transition effects can be set through the JavaScript transition depending on the state of the component.

<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js" rel="external nofollow"  rel="external nofollow"  rel="external nofollow" ></script>
<div id="dynamic-fade-demo">
  Fade In: <input type="range" v-model="fadeInDuration" min="0" v-bind:max="maxFadeDuration">
  Fade Out: <input type="range" v-model="fadeOutDuration" min="0" v-bind:max="maxFadeDuration">
  <transition
    v-bind:css="false"
    v-on:before-enter="beforeEnter"
    v-on:enter="enter"
    v-on:leave="leave"
  >
    <p v-if="show">hello</p>
  </transition>
  <button v-on:click="stop = true">Stop it!</button>
</div>
new Vue({
  el: '#dynamic-fade-demo',
  data: {
    show: true,
    fadeInDuration: 1000,
    fadeOutDuration: 1000,
    maxFadeDuration: 1500,
    stop: false
  },
  mounted: function () {
    this.show = false
  },
  methods: {
    beforeEnter: function (el) {
      el.style.opacity = 0
    },
    enter: function (el, done) {
      var vm = this
      Velocity(el,
        { opacity: 1 },
        {
          duration: this.fadeInDuration,
          complete: function () {
            done()
            if (!vm.stop) vm.show = false
          }
        }
      )
    },
    leave: function (el, done) {
      var vm = this
      Velocity(el,
        { opacity: 0 },
        {
          duration: this.fadeOutDuration,
          complete: function () {
            done()
            vm.show = true
          }
        }
      )
    }
  }
})

Results:

Fade In: Fade Out:

hello