08-Vue过渡动画

2020-04-25  本文已影响0人  仰望_IT

一、基本介绍

1,<transition> 组件

(1)如果某个元素或者组件需要使用过渡动画效果,只需使用 vue 提供的 <transition> 组件将其包裹起来封装成过渡组件。
(2)Vue 只有在插入,更新或者移除 DOM 元素时才会应用过渡效果,例如:

2,过渡效果实现方式

过渡效果具体的实现方式分为如下几种:

3,动画执行逻辑

封装成过渡组件的元素被插入或者删除时,vue 将会做如下事情:

二、结合 CSS 实现动画效果

1,基本用法

(1)在组件过渡过程中,会有如下六个 CSS 类名进行切换:

image

(2)下面是一个简单的样例:

image
// HTML
<div id="app">
    <button @click="show = !show">显示/隐藏</button>
    <transition>
        <div v-if="show">hello</div>
    </transition>
</div>
// CSS
.v-enter{
    opacity: 0;
}
.v-enter-to{
    opacity: 1;
}
.v-enter-active{
    transition: all 1s;
}
.v-leave{
    opacity: 1;
}
.v-leave-to{
    opacity: 0;
}
.v-leave-active{
    transition: all 1s;
}
// JS
<script>
    new Vue({
        el: '#app',
        data: {
            show: true
        }
    });
</script>

注意

  1. transition 中只能放 一个 元素, 多个元素无效
    如果想给多个元素添加过渡动画, 那么就必须创建多个transition组件
    比如这里, 在一个transition组件放了两个div就会报错, 这个时候应该把两个div分别放在两个transition中
<transition>
    <div v-if="show">hello</div>
    <div v-if="show">hello</div>
</transition>
  1. 初始动画设置
    默认情况下第一次进入的时候没没有动画的。
    如果想一进来就有动画, 我们可以通过给transition添加appear属性的方式,
    告诉Vue第一次进入就需要显示动画
<transition appear>
    <div v-if="show">hello</div>
</transition>
  1. 给多个不同的元素指定不同的动画
    如果有多个不同的元素需要执行不同的过渡动画,那么我们可以通过给transition指定name的方式
    来指定"进入之前/进入之后/进入过程中, 离开之前/离开之后/离开过程中"对应的类名
    来实现不同的元素执行不同的过渡动画
// HTML
<transition appear name="fade">
    <div v-if="show">hello</div>
</transition>
// CSS
.fade-enter-active, .fade-leave-active{
    transition: opacity 1s;
}
.fade-enter, .fade-leave-to{
    opacity: 0;
}

2,自定义过渡类名

(1)前面的样例中六个过渡类名都是根据 transition 的 name 属性自动生成的,我们也可以通过 enter-class、enter-active-class、enter-to-class、leave-class、leave-active-class、leave-to-class 这六个属性来分别定义这六个类名。

<transition name="fade"
  enter-class="fade-in-enter"
  enter-active-class="fade-in-active"
  enter-to-class="fade-in-end"
  leave-class="fade-out-enter"
  leave-active-class="fade-out-active"
  leave-to-class="fade-out-end">
  
  <div v-if="show">hello</div>
</transition>

(2)自定义过渡类名的优先级高于普通的类名,这对于 Vue 的过渡系统和其他第三方 CSS 动画库,如 Animate.css 结合使用十分有用。

<link href="https://cdn.jsdelivr.net/npm/animate.css@3.5.1" 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
  }
})

3,通过 CSS 动画(animation)实现过渡效果

组件过渡效果不但可以通过 CSS 过渡实现,还可以通过 CSS 动画(关键帧动画)实现。
(1)效果

image

(2)样例代码

// HTML
<template>
  <div id="app">
    <button @click="show = !show">显示/隐藏</button>
    <br>
    <transition name="fold">
      <img v-show="show" src="./assets/logo.png">
    </transition>
  </div>
</template>
// JS
<script>
export default {
  name: 'App',
  data: function(){
    return {
      show: true
    }
  }
}
</script>
// CSS
<style>
 .fold-enter-active {
   animation-name: fold-in;
   animation-duration: .5s;
 }
 .fold-leave-active {
   animation-name: fold-out;
   animation-duration: 5.5s;
 }
 @keyframes fold-in {
   0% {
     opacity: 0;
     transform: scale(0.7) rotate(0deg);
   }
   33% {
     opacity: 0.33;
     transform: scale(0.8) rotate(5deg);
   }
   67% {
     opacity: 0.67;
     transform: scale(0.9) rotate(-5deg);
   }
   100% {
     opacity: 1.0;
     transform: scale(1.0) rotate(0deg);
   }
 }
 @keyframes fold-out {
   0% {
     opacity: 1.0;
     transform: scale(1.0) rotate(0deg);
   }
   33% {
     opacity: 0.67;
     transform: scale(0.9) rotate(-5deg);
   }
   67% {
     opacity: 0.33;
     transform: scale(0.8) rotate(5deg);
   }
   100% {
     opacity: 0;
     transform: scale(0.7) rotate(0deg);
   }
 }
</style>

4,显示地指定过渡持续时间

指定显性的过渡持续时间的作用:
(1)在很多情况下,Vue 会自动得出过渡效果的完成时机。默认情况下,Vue 会等待其在过渡效果的根元素的第一个 transitionend 或 animationend 事件。
(2)然而也可以不这样设定——比如,我们可以拥有一个精心编排的一系列过渡效果,其中一些嵌套的内部元素相比于过渡效果的根元素有延迟的或更长的过渡效果。

(1)我们可以用<transition>组件上的 duration 属性定制一个显性的过渡持续时间 (以毫秒计):

<transition :duration="1000">...</transition>

(2)也可以分别指定进入和移出的持续时间:

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

</br>

三、JavaScript 钩子

我们先来看一个案例:

image
在这个案例中, div在执行完最终动画后会直接跳回到原点.
可以发现通过 transition+类名 的方式确实能够实现过渡效果
,但是实现的过渡效果并不能保存动画之后的状态。
因为Vue内部的实现是在过程中动态绑定类名, 过程完成之后删除类名
正是因为删除了类名, 所以不能保存最终的效果。想要解决这个问题, 就需要使用JavaScript钩子.

1,基本介绍

我们可以在 transition 属性中声明 JavaScript 钩子,这些钩子函数可以结合 CSS transitions/animations 使用,也可以单独使用。

// 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: {
    // 进入动画之前
    beforeEnter: function (el) {
        // ...
    },
    // 进入动画执行过程中
    enter: function (el, done) {
        // 当与 CSS 结合使用时, 回调函数 done 是可选的
        done();
    },
    // 进入动画完成之后
    afterEnter: function (el) {
        // ...
    },
    // 进入动画被取消
    enterCancelled: function (el) {
        // ...
    },


    // 离开动画之前    
    beforeLeave: function (el) {
        // ...
    },
    // 离开动画执行过程中
    leave: function (el, done) {
        // 当与 CSS 结合使用时, 回调函数 done 是可选的
        done();
    },
    // 离开动画完成之后
    afterLeave: function (el) {
        // ...
    },
    // 离开动画被取消
    // leaveCancelled 只用于 v-show 中
    leaveCancelled: function (el) {
        // ...
    }
}
推荐对于仅使用 JavaScript 过渡的元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响。

注意:

  1. 在动画过程中必须写上 el.offsetWidth 或者 el.offsetHeight
  2. 当只用 JavaScript 过渡的时候,在 enterleave 方法中必须调用 done 方法, 否则 after-enterafter-leave 不会执行,它们将被同步调用,过渡会立即完成。
  3. 需要需要添加初始动画, 那么需要把 done 方法包裹到 setTimeout 方法中调用

现在我们就可以用JavaScript钩子来解决上面的案例中存在的问题:

// HTML
<div id="app">
    <button @click="show = !show">显示/隐藏</button>
    <transition appear
        v-bind:css="false"
        v-on:before-enter="beforeEnter"
        v-on:enter="enter"
        v-on:after-enter="afterEnter"
    >
        <div class="box" v-show="show"></div>
    </transition>
</div>
// CSS
<style>
    .box{
        width: 300px;
        height: 300px;
        background: red;
    }
</style>
// JS
<script>
    new Vue({
        el: '#app',
        data: {
            show: true
        },
        methods: {
            // 进入动画开始之前
            beforeEnter(el){
                el.style.opacity = '0';
            },
            // 进入动画执行过程中
            enter(el, done){
                /*
                注意: 如果是通过JS钩子来实现过渡动画,那么必须在动画执行过程中的回调函数中写上
                        el.offsetWidth / el.offsetHeight
                * */
                el.offsetWidth;
                el.style.transition = 'all 3s';
                // 注意: 动画执行完毕之后一定要调用done回调函数,否则后续的afterEnter钩子函数不会被执行
                // done();
                // 注意: 如果想让元素一进来就有动画, 那么最好延迟以下再调用done方法
                setTimeout(function () {
                    done();
                },0)
            },
            // 进入动画执行完毕之后
            afterEnter(el){
                el.style.opacity = '1';
                el.style.marginLeft = '500px';
            }
        }
    });
</script>

2,配合Velocity实现过渡动画

在Vue中我们除了可以自己实现过渡动画以外, 还可以结合第三方框架实现过渡动画
还是上面的例子, 但是通过Velocity.js来实现会简单很多

  1. 导入Velocity库
<script src="https://cdnjs.cloudflare.com/ajax/libs/velocity/1.2.3/velocity.min.js"></script>
  1. 在动画执行过程钩子函数中编写Velocity动画
methods: {
    beforeEnter(el){
    },
    enter(el, done){
        Velocity(el, {opacity: 1, marginLeft: '500px'}, 3000);
        done();
    },
    afterEnter(el){
    }
}

</br>

四、多个元素、组件的过渡

1,多个元素的过渡

(1)对于原生标签我们可以使用 v-if/v-else 实现多元素过渡。下面是一个列表显示、数据空提示之间的过渡:

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

(2)如果有两个以上的元素过渡可以这么写:

注意: 当有相同标签名的元素切换时,需要通过 key 属性设置唯一的值进行标记,从而让 Vue 区分它们,否则 Vue 为了效率只会替换相同标签内部的内容,就会导致我们的数据混乱。
<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>

(3)在一些场景中,也可以通过给同一个元素的 key 特性设置不同的状态来代替 v-ifv-else。上面的例子可以重写为:

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

2,过渡模式

过渡模式常常配合多个元素或者多个组件切换时使用,有如下两种模式:

<transition mode="out-in">
    <button @click="show=!show" v-if="show" :key=1>on</button>
    <button @click="show=!show" v-else :key=2>off</button>
</transition>

3,多个组件的过渡

多个组件的过渡简单很多 - 我们不需要使用 key attribute。相反,我们只需要使用动态组件:

<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>'
    }
  }
})
.component-fade-enter-active, .component-fade-leave-active {
  transition: opacity .3s ease;
}
.component-fade-enter, .component-fade-leave-to
/* .component-fade-leave-active for below version 2.1.8 */ {
  opacity: 0;
}

五、列表过渡

在此之前我们都是使用 <transition> 组件来实现过渡,其主要用于单个节点、或同一时间渲染多个节点中的一个这两种情况。而对于整个列表(比如使用 v-for)的过渡,则需要使用 <transition-group> 组件。

1,<transition-group> 说明

(1)不同于 <transition><transition-group> 会以一个真实元素呈现:默认为一个 <span>(我们可以通过 tag 属性更换为其他元素。)
(2)过渡模式不可用,因为我们不再相互切换特有的元素。
(3)<transition-group> 的内部元素总是需要提供唯一的 key 属性值。
(4)transition-grouptransition的用法基本一致, 只是一个是给单个元素添加动画, 一个是给多个元素添加动画而已

2,列表的进入、离开过渡

(1)效果图

image

(2)样例代码

// HTML
<template>
  <div id="app">
    <div id="list-demo" class="demo">
    <button v-on:click="add">插入一个元素</button>
    <button v-on:click="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>
  </div>
</template>
// CSS
<style>
  /** 方块元素的样式 **/
  .list-item {
    display: inline-block;
    margin-right: 10px;
    background-color: orange;
    width: 30px;
    height: 30px;
    line-height: 30px;
    text-align: center;
    color: #ffffff;
  }
  /** 插入过程 **/
  .list-enter-active{
    transition: all 1s;
  }
  /** 移除过程 **/
  .list-leave-active {
    transition: all 1s;
  }
  /*** 开始插入、移除结束的位置变化 ***/
  .list-enter, .list-leave-to {
    opacity: 0;
    transform: translateY(30px);
  }
</style>
// JS
<script>
export default {
  name: 'App',
  data: function(){
    return {
      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)
    },
  }
}
</script>

3,列表的排序过渡

(1)上面的样例有个问题:虽然新插入的元素或者被移除的元素有动画效果,但它周围的元素会瞬间移动到他们新布局的位置,而不是平滑的过渡。要解决这个问题则需要借助新增的 v-move 特性。

v-move 特性会在元素改变定位的过程中应用,它像之前的类名一样:

  • 可以通过 name 属性来自定义前缀(比如 name="xxxx",那么对应的类名便是 xxx-move
  • 也可以通过 move-class 属性手动设置自定义类名。

(2)这里对之前样例的 css 部分稍作修改,可以发现在插入或移出过程中,其它元素也会从原来的位置平滑过渡新的位置。

image
// CSS
<style>
  /** 方块元素的样式 **/
  .list-item {
    display: inline-block;
    margin-right: 10px;
    background-color: orange;
    width: 30px;
    height: 30px;
    line-height: 30px;
    text-align: center;
    color: #ffffff;
  }
  /** 插入过程 **/
  .list-enter-active{
    transition: all 1s;
  }
  /** 移除过程 **/
  .list-leave-active {
    transition: all 1s;
    position: absolute;
  }
  /*** 开始插入、移除结束的位置变化 ***/
  .list-enter, .list-leave-to {
    opacity: 0;
    transform: translateY(30px);
  }
  /*** 元素定位改变时动画 ***/
  .list-move {
    transition: transform 1s;
  }
</style>

(3)Vue 使用了一个叫 FLIP 简单的动画队列实现排序过渡。所以即使没有插入或删除元素,对于元素顺序的变化,也是支持过渡动画的。

image
// HTML
<template>
  <div id="app">
    <div id="list-demo" class="demo">
    <button v-on:click="shuffle">乱序</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>
  </div>
</template>
// CSS
<style>
  /** 方块元素的样式 **/
  .list-item {
    display: inline-block;
    margin-right: 10px;
    background-color: orange;
    width: 30px;
    height: 30px;
    line-height: 30px;
    text-align: center;
    color: #ffffff;
  }
  /*** 元素定位改变时动画 ***/
  .list-move {
    transition: transform 1s;
  }
</style>
// JS
<script>
export default {
  name: 'App',
  data: function(){
    return {
      items: [1,2,3,4,5,6,7,8,9]
    }
  },
  methods: {
    shuffle: function () {
      return this.items.sort(function(a,b){ return Math.random()>.5 ? -1 : 1;})
    }
  }
}
</script>

(4)FLIP 动画不仅可以实现单列过渡,多维网格也同样可以过渡:

image
// HTML
<template>
  <div id="app">
    <div id="list-demo" class="demo">
    <button v-on:click="shuffle">乱序</button>
    <transition-group name="cell" tag="div" class="container">
      <div v-for="cell in cells" :key="cell.id" class="cell">
        {{ cell.number }}
      </div>
    </transition-group>
  </div>
  </div>
</template>
// CSS
<style>
  .container {
    display: flex;
    flex-wrap: wrap;
    width: 238px;
    margin-top: 10px;
  }
  .cell {
    display: flex;
    justify-content: space-around;
    align-items: center;
    width: 25px;
    height: 25px;
    border: 1px solid #aaa;
    margin-right: -1px;
    margin-bottom: -1px;
  }
  .cell:nth-child(3n) {
    margin-right: 0;
  }
  .cell:nth-child(27n) {
    margin-bottom: 0;
  }
  .cell-move {
    transition: transform 20s;
  }
</style>
// JS
<script>
export default {
  name: 'App',
  data: function(){
    return {
      cells: Array.apply(null, { length: 81 })
        .map(function (_, index) {
            return {
            id: index,
            number: index % 9 + 1
          }
        })
    }
  },
  methods: {
    shuffle: function () {
      this.cells.sort(function(a,b){ return Math.random()>.5 ? -1 : 1;})
    }
  }
}
</script>

(5)使用 js 钩子函数实现列表的交错过渡
我们也可以通过 data 属性与 JavaScript 通信,实现列表的交错过渡。
1,效果图
(1)在上方输入框中输入内容时,下方的列表会实时筛选并显示出包含该文字的条目。
(2)同时在列表条目的显示或者移出过程中,会有相应的过渡动画。

image

2,样例代码

、、 HTML
<template>
  <div id="app">
    <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>
  </div>
</template>
// JS
<script>
import  Velocity from 'velocity-animate'
 
export default {
  name: 'App',
  data: function(){
    return {
      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 , duration: 20000 }
        )
      }, delay)
    },
    leave: function (el, done) {
      var delay = el.dataset.index * 150
      setTimeout(function () {
        Velocity(
          el,
          { opacity: 0, height: 0 },
          { complete: done , duration: 20000 }
        )
      }, delay)
    }
  }
}
</script>
上一篇 下一篇

猜你喜欢

热点阅读