Vue 动画完全教程

第一章:Vue 动画基础

1.1 过渡系统概述

Vue 的过渡系统提供了多种方式来实现动画效果。

练习 1.1:基本过渡示例

展示 Vue 的基本过渡效果。

基本过渡示例
<template>
  <div>
    <button @click="show = !show">切换</button>
    <transition name="fade">
      <p v-if="show">Hello Vue!</p>
    </transition>
  </div>
</template>

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

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

1.2 过渡类名

Vue 过渡系统提供了 6 个类名用于定义过渡效果。

练习 1.2:过渡类名示例

展示所有过渡类名的使用。

过渡类名示例
<template>
  <div>
    <button @click="show = !show">切换</button>
    <transition name="slide">
      <p v-if="show">滑动效果</p>
    </transition>
  </div>
</template>

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

<style>
/* 进入前 */
.slide-enter-from {
  transform: translateX(-100%);
}

/* 进入过程 */
.slide-enter-active {
  transition: transform 0.5s ease;
}

/* 进入后 */
.slide-enter-to {
  transform: translateX(0);
}

/* 离开前 */
.slide-leave-from {
  transform: translateX(0);
}

/* 离开过程 */
.slide-leave-active {
  transition: transform 0.5s ease;
}

/* 离开后 */
.slide-leave-to {
  transform: translateX(100%);
}
</style>

第二章:CSS 动画

2.1 CSS 动画基础

使用 CSS 动画实现更复杂的过渡效果。

练习 2.1:CSS 动画示例

展示 CSS 动画的使用。

CSS 动画示例
<template>
  <div>
    <button @click="show = !show">切换</button>
    <transition name="bounce">
      <p v-if="show">弹跳效果</p>
    </transition>
  </div>
</template>

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

<style>
.bounce-enter-active {
  animation: bounce-in 0.5s;
}
.bounce-leave-active {
  animation: bounce-in 0.5s reverse;
}
@keyframes bounce-in {
  0% {
    transform: scale(0);
  }
  50% {
    transform: scale(1.25);
  }
  100% {
    transform: scale(1);
  }
}
</style>

2.2 自定义过渡类名

使用自定义类名实现第三方动画库的集成。

练习 2.2:自定义类名示例

展示自定义过渡类名的使用。

自定义类名示例
<template>
  <div>
    <button @click="show = !show">切换</button>
    <transition
      enter-active-class="animate__animated animate__bounceIn"
      leave-active-class="animate__animated animate__bounceOut"
    >
      <p v-if="show">Animate.css 效果</p>
    </transition>
  </div>
</template>

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

<style>
@import 'https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css';
</style>

第三章:JavaScript 钩子

3.1 过渡钩子函数

使用 JavaScript 钩子实现更复杂的动画效果。

练习 3.1:JavaScript 钩子示例

展示 JavaScript 钩子的使用。

JavaScript 钩子示例
<template>
  <div>
    <button @click="show = !show">切换</button>
    <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">JavaScript 动画</p>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    beforeEnter(el) {
      el.style.opacity = 0
      el.style.transform = 'translateY(100px)'
    },
    enter(el, done) {
      const animation = el.animate([
        { opacity: 0, transform: 'translateY(100px)' },
        { opacity: 1, transform: 'translateY(0)' }
      ], {
        duration: 1000,
        easing: 'ease-in-out'
      })
      animation.onfinish = done
    },
    afterEnter(el) {
      el.style.opacity = ''
      el.style.transform = ''
    },
    enterCancelled(el) {
      el.style.opacity = ''
      el.style.transform = ''
    },
    beforeLeave(el) {
      el.style.opacity = 1
      el.style.transform = 'translateY(0)'
    },
    leave(el, done) {
      const animation = el.animate([
        { opacity: 1, transform: 'translateY(0)' },
        { opacity: 0, transform: 'translateY(100px)' }
      ], {
        duration: 1000,
        easing: 'ease-in-out'
      })
      animation.onfinish = done
    },
    afterLeave(el) {
      el.style.opacity = ''
      el.style.transform = ''
    },
    leaveCancelled(el) {
      el.style.opacity = ''
      el.style.transform = ''
    }
  }
}
</script>

3.2 使用 Velocity.js

集成第三方动画库 Velocity.js。

练习 3.2:Velocity.js 示例

展示 Velocity.js 的使用。

Velocity.js 示例
<template>
  <div>
    <button @click="show = !show">切换</button>
    <transition
      @enter="enter"
      @leave="leave"
      :css="false"
    >
      <p v-if="show">Velocity.js 动画</p>
    </transition>
  </div>
</template>

<script>
import Velocity from 'velocity-animate'

export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    enter(el, done) {
      Velocity(el, 
        { opacity: 1, translateY: 0 },
        { 
          duration: 1000,
          easing: 'ease-in-out',
          complete: done
        }
      )
    },
    leave(el, done) {
      Velocity(el,
        { opacity: 0, translateY: '100px' },
        {
          duration: 1000,
          easing: 'ease-in-out',
          complete: done
        }
      )
    }
  }
}
</script>

第四章:列表过渡

4.1 列表过渡基础

使用 transition-group 实现列表项的过渡效果。

练习 4.1:列表过渡示例

展示列表过渡的基本使用。

列表过渡示例
<template>
  <div>
    <button @click="addItem">添加项目</button>
    <button @click="removeItem">删除项目</button>
    <transition-group name="list" tag="ul">
      <li v-for="item in items" :key="item">
        {{ item }}
      </li>
    </transition-group>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [1, 2, 3, 4, 5],
      nextNum: 6
    }
  },
  methods: {
    addItem() {
      this.items.push(this.nextNum++)
    },
    removeItem() {
      this.items.pop()
    }
  }
}
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateY(30px);
}
.list-move {
  transition: transform 0.5s ease;
}
</style>

4.2 列表排序动画

实现列表项的排序动画效果。

练习 4.2:列表排序动画示例

展示列表排序动画的实现。

列表排序动画示例
<template>
  <div>
    <button @click="shuffle">随机排序</button>
    <transition-group name="flip-list" tag="ul">
      <li v-for="item in items" :key="item">
        {{ item }}
      </li>
    </transition-group>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
  },
  methods: {
    shuffle() {
      this.items = _.shuffle(this.items)
    }
  }
}
</script>

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

第五章:状态过渡

5.1 数字过渡

实现数字的平滑过渡效果。

练习 5.1:数字过渡示例

展示数字过渡的实现。

数字过渡示例
<template>
  <div>
    <input v-model.number="number" type="number" step="20">
    <p>{{ animatedNumber }}</p>
  </div>
</template>

<script>
import gsap from 'gsap'

export default {
  data() {
    return {
      number: 0,
      tweenedNumber: 0
    }
  },
  computed: {
    animatedNumber() {
      return this.tweenedNumber.toFixed(0)
    }
  },
  watch: {
    number(newValue) {
      gsap.to(this, { 
        duration: 0.5, 
        tweenedNumber: newValue 
      })
    }
  }
}
</script>

5.2 颜色过渡

实现颜色的平滑过渡效果。

练习 5.2:颜色过渡示例

展示颜色过渡的实现。

颜色过渡示例
<template>
  <div>
    <input v-model="colorQuery" placeholder="输入颜色">
    <div class="color-box" :style="{ backgroundColor: tweenedCSSColor }"></div>
  </div>
</template>

<script>
import gsap from 'gsap'
import Color from 'color'

export default {
  data() {
    return {
      colorQuery: '',
      color: {
        red: 0,
        green: 0,
        blue: 0,
        alpha: 1
      },
      tweenedColor: {}
    }
  },
  computed: {
    tweenedCSSColor() {
      return new Color({
        red: this.tweenedColor.red,
        green: this.tweenedColor.green,
        blue: this.tweenedColor.blue,
        alpha: this.tweenedColor.alpha
      }).rgb().string()
    }
  },
  watch: {
    colorQuery(newValue) {
      const color = Color(newValue)
      if (color.isValid()) {
        this.color = {
          red: color.red(),
          green: color.green(),
          blue: color.blue(),
          alpha: color.alpha()
        }
      }
    },
    color: {
      handler(newValue) {
        gsap.to(this.tweenedColor, {
          duration: 0.5,
          ...newValue
        })
      },
      deep: true
    }
  },
  created() {
    this.tweenedColor = { ...this.color }
  }
}
</script>

<style>
.color-box {
  width: 100px;
  height: 100px;
  margin-top: 10px;
}
</style>

第六章:高级动画技巧

6.1 动画性能优化

优化动画性能的技巧和方法。

练习 6.1:性能优化示例

展示动画性能优化的实现。

性能优化示例
<template>
  <div>
    <button @click="show = !show">切换</button>
    <transition
      @before-enter="beforeEnter"
      @enter="enter"
      @leave="leave"
      :css="false"
    >
      <div v-if="show" class="box"></div>
    </transition>
  </div>
</template>

<script>
import gsap from 'gsap'

export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    beforeEnter(el) {
      el.style.transform = 'translateX(-100px)'
      el.style.opacity = 0
    },
    enter(el, done) {
      gsap.to(el, {
        x: 0,
        opacity: 1,
        duration: 0.5,
        ease: 'power2.out',
        onComplete: done,
        // 使用 will-change 优化性能
        onStart: () => {
          el.style.willChange = 'transform, opacity'
        },
        onComplete: () => {
          el.style.willChange = 'auto'
          done()
        }
      })
    },
    leave(el, done) {
      gsap.to(el, {
        x: 100,
        opacity: 0,
        duration: 0.5,
        ease: 'power2.in',
        onComplete: done
      })
    }
  }
}
</script>

<style>
.box {
  width: 100px;
  height: 100px;
  background-color: #42b983;
  /* 使用 transform 代替位置属性 */
  transform: translateZ(0);
  /* 启用硬件加速 */
  backface-visibility: hidden;
  perspective: 1000px;
}
</style>

6.2 动画组合

实现复杂的动画组合效果。

练习 6.2:动画组合示例

展示动画组合的实现。

动画组合示例
<template>
  <div>
    <button @click="show = !show">切换</button>
    <transition
      @before-enter="beforeEnter"
      @enter="enter"
      @leave="leave"
      :css="false"
    >
      <div v-if="show" class="card">
        <div class="card-content">
          <h3>标题</h3>
          <p>内容</p>
        </div>
      </div>
    </transition>
  </div>
</template>

<script>
import gsap from 'gsap'

export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    beforeEnter(el) {
      gsap.set(el, {
        scale: 0.8,
        opacity: 0
      })
    },
    enter(el, done) {
      const tl = gsap.timeline({
        onComplete: done
      })
      
      tl.to(el, {
        scale: 1,
        opacity: 1,
        duration: 0.5,
        ease: 'back.out(1.7)'
      })
      .to('.card-content', {
        y: 0,
        opacity: 1,
        duration: 0.3,
        ease: 'power2.out'
      }, '-=0.2')
    },
    leave(el, done) {
      const tl = gsap.timeline({
        onComplete: done
      })
      
      tl.to('.card-content', {
        y: 20,
        opacity: 0,
        duration: 0.2,
        ease: 'power2.in'
      })
      .to(el, {
        scale: 0.8,
        opacity: 0,
        duration: 0.3,
        ease: 'back.in(1.7)'
      }, '-=0.1')
    }
  }
}
</script>

<style>
.card {
  width: 300px;
  padding: 20px;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  transform-origin: center;
}

.card-content {
  transform: translateY(20px);
  opacity: 0;
}

h3 {
  margin: 0 0 10px 0;
}

p {
  margin: 0;
  color: #666;
}
</style>

学习建议

建议按照章节顺序学习,每完成一个练习后再进行下一个。可以通过创建实际项目来实践所学知识。特别注意动画性能优化和用户体验。