Vue中transition过渡组件全掌握

15,327 阅读7分钟

view4

在冷冰冰的网页上,加上些许过渡或动画,其变化虽小,却能极大的提升页面质感,给人一种顺畅、丝滑的视觉体验。它的实现过程主要是通过css中的transitionanimation来实现的。

而在vue框架中也对此进行了封装,提供了便捷的过渡用法。

1、transition组件用法

在业务开发过程中,最常用的还是transition组件的用法:

<transition name="slide-fade">
  <p v-if="show"> hello world </p>
</transition>
/* 可以设置不同的进入和离开动画 */
/* 设置持续时间和动画函数 */
.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-to
  transform: translateX(10px);
  opacity: 0;
}

在进入/离开的过渡中,主要有 6 个 钩子被触发,并对应6个class 切换。

状态过渡变化

如果你的transition组件没有设置name属性,则 v- 是这些类名的默认前缀。如果你使用了<transition name="my-transition"> ,那么 v-enter 会替换为 my-transition-enter

这6种class类名可以自定义,他们的优先级高于普通的类名:

<transition name="slide-fade" 
            enter-active-class="custom-enter-active-class"
            leave-active-class="custom-leave-active-class">
  <p v-if="show"> hello world </p>
</transition>
/* css部分 */
.custom-enter-active-class {
  animation: bounce-in .5s;
}
.custom-leave-active-class {
  animation: bounce-in .5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.5);
  }
  100% {
    transform: scale(1);
  }
}

当有相同标签名的元素切换时,推荐<transition> 组件中的多个元素设置 key 让vue区分它们

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

该例子可以用key不同的值代替v-if

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

另外在不同组件中切换过渡:

<transition name="component-fade" mode="out-in">
  <component :is="view"></component>
</transition>
export default {
  data () {
    return {
    	view: 'v-a'
  	}
  },
  components: {
    'v-a': {
      template: '<div>Component A</div>'
    },
    'v-b': {
      template: '<div>Component B</div>'
    }
  }
}

默认情况下,上述两个组件的进入和离开是同时发生的,一个离开过渡的时候另一个开始进入过渡,只有离开的组件完成动画之后,才会把位置让出来,通常这会造成一种卡顿的效果(如果两个元素都设置了绝对定位,则没有这个问题)。

可以通过添加过渡模式mode来满足我们的需求:

  • in-out:新元素先进行过渡,完成之后当前元素过渡离开。
  • out-in:当前元素先进行过渡,完成之后新元素过渡进入。

通常我们更多的会使用out-in,让当前元素先离开,然后再进行新元素的进入。

<transition name="fade" mode="out-in">
  <!-- 不同元素 -->
</transition>

了解了以上的用法,基本就可以实现大部分业务场景了。

2、使用JavaScript动态过渡

在大部分场景下,我们主要是使用css来进行过渡,css已经前置写好了,但在某些情况下,我们还需要动态过渡。

动态过渡最基本的操作就是绑定动态属性,比如name,当有不同的情况时切换不同的过渡效果。

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

不过这同样需要我们提前写好对应的过渡效果,除此之外,最直接的实现动态过渡的方式是使用JavaScript过渡:

<transition
  @before-enter="beforeEnter"
  @enter="enter"
  @after-enter="afterEnter"
  @enter-cancelled="enterCancelled"

  @before-leave="beforeLeave"
  @leave="leave"
  @after-leave="afterLeave"
  @leave-cancelled="leaveCancelled"
>
  <!-- ... -->
</transition>
methods: {
  // 过渡进入之前触发
  beforeEnter: function (el) { },
  // 过渡进入时触发
  enter: function (el, done) {
    // ...
    // 当与 CSS 结合使用时,回调函数 done 是可选的
    // 不用CSS过渡,设置了:css="false",则必须使用回调done 
    done()
  },
  // 过渡进入结束时触发
  afterEnter: function (el) { },
  // 过渡进入被取消时触发
  enterCancelled: function (el) { },
	// 在离开之前触发
  beforeLeave: function (el) { },
  // 离开时触发
  leave: function (el, done) {
    // ...
    // 当与 CSS 结合使用,回调函数 done 是可选的
    // 不用CSS过渡,设置了:css="false",则必须使用回调done 
    done()
  },
  // 离开后触发
  afterLeave: function (el) { },
  // 离开过程被取消时触发(只用于 v-show 中)
  leaveCancelled: function (el) { }
}

上面这些JavaScript过渡钩子可以和 CSS transitions/animations 同时使用,也可以单独使用。

不过既然你都使用了 JavaScript 过渡,那么推荐就不用CSS过渡,元素添加 v-bind:css="false",Vue 会跳过 CSS 的检测。这也可以避免过渡过程中 CSS 的影响

如这个例子:

<!-- Velocity 和 jQuery.animate 的工作方式类似,也是用来实现 JavaScript 动画的一个很棒的选择 -->
<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
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
    :css="false"
  >
    <p v-if="show">
      Demo
    </p>
  </transition>
</div>
new Vue({
  el: '#example-4',
  data: {
    show: false
  },
  methods: {
    // 钩子函数里写具体的过渡数据,可以实现js动态控制
    beforeEnter: function (el) {
      el.style.opacity = 0
      el.style.transformOrigin = 'left'
    },
    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 })
    }
  }
})

3、初始渲染appear

上述过渡的用法通常需要元素或者组件有变化才会触发,如需要v-ifv-show动态组件等。

如果你的需求是第一次进页面的时候,它就过渡渲染出来,之后是不需要再过渡的,那可以使用appear这个属性。如下面这个示例:

view2

<!-- appear 也可以自定义 CSS 类名 --> 
<transition appear
            appear-class="custom-appear-class"
            appear-active-class="custom-appear-active-class"
            appear-to-class="custom-appear-to-class">
  <p>我是一段测试appear初始渲染文字</p>
</transition>
.custom-appear-class{
  opacity: 0;
}
.custom-appear-active-class{
  transition: all 3s;
}
.custom-appear-to-class{
  opacity: 1;
}

appearenter有点像,但它只在初始渲染的时候触发,并且只要把触发之前和触发之后的状态设置不同,便可以实现中间的自然过渡。

同时,它也可以用JavaScript钩子函数实现:

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

4、列表过渡和v-move

同一时间内渲染单个节点,或者多个节点中的一个,使用<transition>组件,如果同时渲染多个节点,比如整个列表,则需要用<transition-group>组件,该组件有几个特点:

  • 不同于 <transition>,它会以一个真实元素呈现:默认为一个 <span>。可以通过 tag 属性更换为其他元素。
  • 列表渲染中没有过渡模式。
  • 内部元素必须要提供key值。
<!-- 设置tag属性为p元素,v-for中的每一项设置key值 -->
<!-- items为一个连续数字组成的数组 [0, 1, 2, ...] -->
<transition-group name="list" tag="p" class="list-item">
   <span v-for="item in items" :key="item">
     {{ item }}
   </span>
</transition-group>
.list-item {
  display: inline-block;
  margin-right: 10px;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
.list-enter, .list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}

view3

效果如上,但是这有一个问题:当添加和移除元素的时候,周围的元素会瞬间移动到他们的新布局的位置,有一种卡顿的感觉。

要解决这个问题可以通过<transition-group>组件中的 v-move class,它会在元素的改变定位的过程中应用。像之前的类名一样,可以通过 name attribute 来自定义前缀,也可以通过 move-class attribute 手动设置。

于是只要在上面的例子中修改一下css:

.list-item {
  display: inline-block;
  margin-right: 10px;
}
/* 这里增加和一个.list-move类样式,它会在元素改变的过程中生效 */
.list-move{
  transition: all 1s;
}
.list-enter-active, .list-leave-active {
  transition: all 1s;
}
/* 这里在元素离开时增加一个绝对定位属性 */
/* 这样元素离开的瞬间就会触发改变位置,使上面的.list-move生效 */
/* 如果不加绝对定位,则元素离开动作完成后周围的元素才会移动到它的位置 */
/* 而那个时候所有过渡已结束 .list-move已经被移除,会造成卡顿感觉 */
.list-leave-active {
  position: absolute;
}
.list-enter, .list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}

view4

如此就有了一种丝滑的感觉,另外上述css可进一步修改简化:

/* 直接在元素上增加transition属性 */
/* 它会在元素改变的所有过程中生效,包括v-move的时候 */
.list-item {
  display: inline-block;
  margin-right: 10px;
  transition: all 1s;
}
/* 因此v-move和v-active相关的均可不需要了 */
/* 只要在元素离开时增加一个绝对定位属性即可 */
.list-leave-active {
  position: absolute;
}
.list-enter, .list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}

注意:v-move的内部实现,Vue 使用了一个叫 FLIP 简单的动画队列。

使用 FLIP 过渡的元素不能设置为 display: inline

所以可以设置为 display: inline-block 或者放置于 flex 中。