Skip to content

Vue2 过渡与动画 ​

单元素/组件过渡 ​

transition 组件 ​

Vue 提供了 transition 组件来为元素或组件的进入/离开添加过渡效果。

vue
<template>
  <div>
    <button @click="show = !show">切换</button>

    <transition name="fade">
      <p v-if="show">淡入淡出效果</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return { show: true }
  }
}
</script>

<style>
/* 定义过渡类名 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
<template>
  <div>
    <button @click="show = !show">切换</button>

    <transition name="fade">
      <p v-if="show">淡入淡出效果</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return { show: true }
  }
}
</script>

<style>
/* 定义过渡类名 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>

过渡类名 ​

进入:
v-enter-from     → v-enter-active    → v-enter-to
(进入前)       (进入过程)         (进入后)

离开:
v-leave-from     → v-leave-active    → v-leave-to
(离开前)       (离开过程)         (离开后)
进入:
v-enter-from     → v-enter-active    → v-enter-to
(进入前)       (进入过程)         (进入后)

离开:
v-leave-from     → v-leave-active    → v-leave-to
(离开前)       (离开过程)         (离开后)

若 transition 未设置 name,类名前缀为 v-;设置后使用自定义前缀:

vue
<template>
  <transition name="slide">
    <p v-if="show">滑动效果</p>
  </transition>
</template>

<style>
.slide-enter-active,
.slide-leave-active {
  transition: all 0.3s ease;
}

.slide-enter-from {
  transform: translateX(100%);
  opacity: 0;
}

.slide-leave-to {
  transform: translateX(-100%);
  opacity: 0;
}
</style>
<template>
  <transition name="slide">
    <p v-if="show">滑动效果</p>
  </transition>
</template>

<style>
.slide-enter-active,
.slide-leave-active {
  transition: all 0.3s ease;
}

.slide-enter-from {
  transform: translateX(100%);
  opacity: 0;
}

.slide-leave-to {
  transform: translateX(-100%);
  opacity: 0;
}
</style>

CSS 动画 ​

vue
<template>
  <transition name="bounce">
    <p v-if="show">弹跳效果</p>
  </transition>
</template>

<style>
@keyframes bounce-in {
  0% { transform: scale(0); }
  50% { transform: scale(1.25); }
  100% { transform: scale(1); }
}

.bounce-enter-active {
  animation: bounce-in 0.5s;
}

.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
</style>
<template>
  <transition name="bounce">
    <p v-if="show">弹跳效果</p>
  </transition>
</template>

<style>
@keyframes bounce-in {
  0% { transform: scale(0); }
  50% { transform: scale(1.25); }
  100% { transform: scale(1); }
}

.bounce-enter-active {
  animation: bounce-in 0.5s;
}

.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
</style>

自定义过渡类名(配合第三方动画库) ​

vue
<template>
  <!-- 使用 animate.css -->
  <transition
    enter-active-class="animate__animated animate__fadeIn"
    leave-active-class="animate__animated animate__fadeOut"
  >
    <p v-if="show">使用 animate.css</p>
  </transition>
</template>

<style>
@import 'animate.css';
</style>
<template>
  <!-- 使用 animate.css -->
  <transition
    enter-active-class="animate__animated animate__fadeIn"
    leave-active-class="animate__animated animate__fadeOut"
  >
    <p v-if="show">使用 animate.css</p>
  </transition>
</template>

<style>
@import 'animate.css';
</style>

初始渲染过渡 ​

appear 属性 ​

vue
<template>
  <transition appear appear-class="custom-appear-class" appear-active-class="custom-active-class">
    <p>页面加载时就有动画</p>
  </transition>
</template>
<template>
  <transition appear appear-class="custom-appear-class" appear-active-class="custom-active-class">
    <p>页面加载时就有动画</p>
  </transition>
</template>

多个元素过渡 ​

使用 key 区分 ​

vue
<template>
  <transition name="fade">
    <!-- 必须添加 key,Vue 通过 key 区分不同元素 -->
    <button :key="'save'" v-if="editing" @click="save">保存</button>
    <button :key="'edit'" v-else @click="edit">编辑</button>
  </transition>
</template>
<template>
  <transition name="fade">
    <!-- 必须添加 key,Vue 通过 key 区分不同元素 -->
    <button :key="'save'" v-if="editing" @click="save">保存</button>
    <button :key="'edit'" v-else @click="edit">编辑</button>
  </transition>
</template>

列表过渡 ​

transition-group ​

vue
<template>
  <div>
    <button @click="addItem">添加</button>
    <button @click="removeItem">移除</button>

    <transition-group name="list" tag="ul">
      <li v-for="item in items" :key="item.id">
        {{ item.text }}
        <button @click="removeItem(item)">x</button>
      </li>
    </transition-group>
  </div>
</template>

<script>
let id = 0
export default {
  data() {
    return { items: [] }
  },
  methods: {
    addItem() {
      this.items.push({ id: id++, text: `项目 ${id}` })
    },
    removeItem(item) {
      this.items = this.items.filter(i => i !== item)
    }
  }
}
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
</style>
<template>
  <div>
    <button @click="addItem">添加</button>
    <button @click="removeItem">移除</button>

    <transition-group name="list" tag="ul">
      <li v-for="item in items" :key="item.id">
        {{ item.text }}
        <button @click="removeItem(item)">x</button>
      </li>
    </transition-group>
  </div>
</template>

<script>
let id = 0
export default {
  data() {
    return { items: [] }
  },
  methods: {
    addItem() {
      this.items.push({ id: id++, text: `项目 ${id}` })
    },
    removeItem(item) {
      this.items = this.items.filter(i => i !== item)
    }
  }
}
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}

.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
</style>

列表移动过渡 ​

vue
<template>
  <transition-group name="flip-list" tag="ul">
    <li v-for="item in items" :key="item.id">{{ item.text }}</li>
  </transition-group>
</template>

<style>
.flip-list-move {
  transition: transform 0.5s ease;
}

.flip-list-enter-active,
.flip-list-leave-active {
  transition: all 0.5s ease;
}

.flip-list-enter-from,
.flip-list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

.flip-list-leave-active {
  /* 使列表项在离开时不影响其他项布局 */
  position: absolute;
}
</style>
<template>
  <transition-group name="flip-list" tag="ul">
    <li v-for="item in items" :key="item.id">{{ item.text }}</li>
  </transition-group>
</template>

<style>
.flip-list-move {
  transition: transform 0.5s ease;
}

.flip-list-enter-active,
.flip-list-leave-active {
  transition: all 0.5s ease;
}

.flip-list-enter-from,
.flip-list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}

.flip-list-leave-active {
  /* 使列表项在离开时不影响其他项布局 */
  position: absolute;
}
</style>

JavaScript 钩子 ​

完整钩子 ​

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

    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
    @leave-cancelled="leaveCancelled"
  >
    <p v-if="show">JS 钩子控制动画</p>
  </transition>
</template>

<script>
export default {
  data() {
    return { show: false }
  },
  methods: {
    beforeEnter(el) {
      // 设置起始状态
      el.style.opacity = '0'
      el.style.transform = 'translateY(20px)'
    },
    enter(el, done) {
      // 强制回流使起始状态生效
      el.offsetHeight

      // 设置结束状态
      el.style.transition = 'all 0.3s ease'
      el.style.opacity = '1'
      el.style.transform = 'translateY(0)'

      // 调用 done 标记动画结束
      done()
    },
    afterEnter(el) {
      console.log('进入动画完成')
    },
    enterCancelled(el) {
      console.log('进入动画取消')
    },
    beforeLeave(el) {
      el.style.opacity = '1'
    },
    leave(el, done) {
      el.style.transition = 'all 0.3s ease'
      el.style.opacity = '0'
      el.style.transform = 'translateY(20px)'
      done()
    },
    afterLeave(el) {
      console.log('离开动画完成')
    }
  }
}
</script>
<template>
  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter"
    @enter-cancelled="enterCancelled"

    @before-leave="beforeLeave"
    @leave="leave"
    @after-leave="afterLeave"
    @leave-cancelled="leaveCancelled"
  >
    <p v-if="show">JS 钩子控制动画</p>
  </transition>
</template>

<script>
export default {
  data() {
    return { show: false }
  },
  methods: {
    beforeEnter(el) {
      // 设置起始状态
      el.style.opacity = '0'
      el.style.transform = 'translateY(20px)'
    },
    enter(el, done) {
      // 强制回流使起始状态生效
      el.offsetHeight

      // 设置结束状态
      el.style.transition = 'all 0.3s ease'
      el.style.opacity = '1'
      el.style.transform = 'translateY(0)'

      // 调用 done 标记动画结束
      done()
    },
    afterEnter(el) {
      console.log('进入动画完成')
    },
    enterCancelled(el) {
      console.log('进入动画取消')
    },
    beforeLeave(el) {
      el.style.opacity = '1'
    },
    leave(el, done) {
      el.style.transition = 'all 0.3s ease'
      el.style.opacity = '0'
      el.style.transform = 'translateY(20px)'
      done()
    },
    afterLeave(el) {
      console.log('离开动画完成')
    }
  }
}
</script>

velocity.js 搭配 ​

vue
<template>
  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
  >
    <p v-if="show">Velocity 动画</p>
  </transition>
</template>

<script>
import Velocity from 'velocity-animate'

export default {
  data() {
    return { show: false }
  },
  methods: {
    beforeEnter(el) {
      el.style.opacity = '0'
    },
    enter(el, done) {
      Velocity(el, { opacity: 1, translateY: '0px' }, { duration: 300, complete: done })
    },
    leave(el, done) {
      Velocity(el, { opacity: 0, translateY: '20px' }, { duration: 300, complete: done })
    }
  }
}
</script>
<template>
  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
  >
    <p v-if="show">Velocity 动画</p>
  </transition>
</template>

<script>
import Velocity from 'velocity-animate'

export default {
  data() {
    return { show: false }
  },
  methods: {
    beforeEnter(el) {
      el.style.opacity = '0'
    },
    enter(el, done) {
      Velocity(el, { opacity: 1, translateY: '0px' }, { duration: 300, complete: done })
    },
    leave(el, done) {
      Velocity(el, { opacity: 0, translateY: '20px' }, { duration: 300, complete: done })
    }
  }
}
</script>