Vue动画效果踩坑

10.1 Vue过渡动画的常见错误

核心知识点

  • Vue过渡动画的基本原理
  • transition组件的正确使用
  • 过渡类名的应用时机

常见错误场景

错误场景1:transition组件包裹多个元素

<template>
  <div>
    <!-- 错误用法:transition包裹多个根元素 -->
    <transition name="fade">
      <div v-if="show">内容1</div>
      <div v-else>内容2</div>
    </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>

错误原因:transition组件只能包裹一个根元素,否则动画效果不会生效。

正确实现

<template>
  <div>
    <!-- 正确用法:transition包裹单个根元素 -->
    <transition name="fade">
      <div v-if="show">内容1</div>
      <div v-else key="content2">内容2</div>
    </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>

错误场景2:没有为切换的元素添加key属性

<template>
  <div>
    <transition name="fade">
      <!-- 错误:没有添加key属性 -->
      <div v-if="isLogin">登录表单</div>
      <div v-else>注册表单</div>
    </transition>
  </div>
</template>

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

错误原因:当切换的元素类型相同时,Vue会复用元素,导致动画效果不明显。

正确实现

<template>
  <div>
    <transition name="fade">
      <!-- 正确:添加key属性 -->
      <div v-if="isLogin" key="login">登录表单</div>
      <div v-else key="register">注册表单</div>
    </transition>
  </div>
</template>

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

10.2 Vue动画钩子函数的使用误区

核心知识点

  • Vue过渡动画的钩子函数
  • 钩子函数的执行时机
  • 钩子函数的参数使用

常见错误场景

错误场景1:在钩子函数中直接修改DOM

<template>
  <div>
    <transition
      @enter="enterHook"
      @leave="leaveHook"
    >
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    enterHook(el) {
      // 错误:直接修改DOM样式
      el.style.opacity = 0
      setTimeout(() => {
        el.style.opacity = 1
      }, 100)
    },
    leaveHook(el) {
      // 错误:直接修改DOM样式
      el.style.opacity = 1
      setTimeout(() => {
        el.style.opacity = 0
      }, 100)
    }
  }
}
</script>

错误原因:直接修改DOM样式可能与Vue的过渡类名冲突,导致动画效果异常。

正确实现

<template>
  <div>
    <transition
      @enter="enterHook"
      @leave="leaveHook"
    >
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    enterHook(el, done) {
      // 正确:使用done回调
      el.style.opacity = 0
      setTimeout(() => {
        el.style.opacity = 1
        done() // 必须调用done
      }, 500)
    },
    leaveHook(el, done) {
      // 正确:使用done回调
      el.style.opacity = 1
      setTimeout(() => {
        el.style.opacity = 0
        done() // 必须调用done
      }, 500)
    }
  }
}
</script>

错误场景2:忘记调用done回调

<template>
  <div>
    <transition
      @enter="enterHook"
    >
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    enterHook(el, done) {
      // 错误:忘记调用done回调
      el.style.transform = 'translateX(100px)'
      setTimeout(() => {
        el.style.transform = 'translateX(0)'
      }, 500)
    }
  }
}
</script>

错误原因:如果提供了done回调但没有调用,动画将永远不会结束。

正确实现

<template>
  <div>
    <transition
      @enter="enterHook"
    >
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

<script>
export default {
  data() {
    return {
      show: true
    }
  },
  methods: {
    enterHook(el, done) {
      // 正确:调用done回调
      el.style.transform = 'translateX(100px)'
      setTimeout(() => {
        el.style.transform = 'translateX(0)'
        done() // 调用done
      }, 500)
    }
  }
}
</script>

10.3 Vue动画性能的优化陷阱

核心知识点

  • Vue动画的性能影响因素
  • 硬件加速的使用
  • 动画优化技巧

常见错误场景

错误场景1:使用复杂的CSS动画

<template>
  <div>
    <transition name="complex">
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

<style>
.complex-enter-active {
  /* 错误:使用复杂的动画属性 */
  animation: complex-animation 1s ease-in-out;
}

@keyframes complex-animation {
  0% {
    opacity: 0;
    transform: translateX(-100px) rotate(0deg);
    background-color: red;
  }
  50% {
    opacity: 0.5;
    transform: translateX(-50px) rotate(180deg);
    background-color: blue;
  }
  100% {
    opacity: 1;
    transform: translateX(0) rotate(360deg);
    background-color: green;
  }
}
</style>

错误原因:复杂的动画会占用大量CPU资源,导致性能问题。

正确实现

<template>
  <div>
    <transition name="simple">
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

<style>
.simple-enter-active {
  /* 正确:使用简单的动画属性 */
  transition: all 0.3s ease;
  /* 启用硬件加速 */
  transform: translateZ(0);
}
.simple-enter-from {
  opacity: 0;
  transform: translateX(-100px);
}
</style>

错误场景2:没有使用硬件加速

<template>
  <div>
    <transition name="fade">
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

<style>
.fade-enter-active,
.fade-leave-active {
  /* 错误:没有启用硬件加速 */
  transition: top 0.5s;
}
.fade-enter-from,
.fade-leave-to {
  top: -100px;
}
</style>

错误原因:使用top、left等属性会触发重排,性能较差。

正确实现

<template>
  <div>
    <transition name="fade">
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

<style>
.fade-enter-active,
.fade-leave-active {
  /* 正确:使用transform启用硬件加速 */
  transition: transform 0.5s;
  transform: translateZ(0); /* 启用硬件加速 */
}
.fade-enter-from,
.fade-leave-to {
  transform: translateY(-100px);
}
</style>

10.4 Vue CSS动画的使用误区

核心知识点

  • CSS动画的基本原理
  • Vue中CSS动画的应用
  • 动画类名的正确使用

常见错误场景

错误场景1:混用transition和animation

<template>
  <div>
    <transition name="mix">
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

<style>
/* 错误:混用transition和animation */
.mix-enter-active {
  transition: opacity 0.5s;
  animation: bounce 1s;
}

.mix-enter-from {
  opacity: 0;
}

@keyframes bounce {
  0% {
    transform: scale(0.8);
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    transform: scale(1);
  }
}
</style>

错误原因:混用transition和animation可能导致动画效果冲突。

正确实现

<template>
  <div>
    <transition name="animation">
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

<style>
/* 正确:只使用animation */
.animation-enter-active {
  animation: bounce 1s;
}

.animation-leave-active {
  animation: bounce 1s reverse;
}

@keyframes bounce {
  0% {
    opacity: 0;
    transform: scale(0.8);
  }
  50% {
    transform: scale(1.2);
  }
  100% {
    opacity: 1;
    transform: scale(1);
  }
}
</style>

错误场景2:使用过时的过渡类名

<template>
  <div>
    <transition name="fade">
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

<style>
/* 错误:使用Vue 2的过时类名 */
.fade-enter,
.fade-leave-to {
  opacity: 0;
}

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

错误原因:Vue 3使用了新的过渡类名,旧的类名在Vue 3中不会生效。

正确实现

<template>
  <div>
    <transition name="fade">
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

<style>
/* 正确:使用Vue 3的过渡类名 */
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

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

10.5 Vue JavaScript动画的陷阱

核心知识点

  • JavaScript动画的实现方式
  • Vue中JavaScript动画的应用
  • 动画的控制和管理

常见错误场景

错误场景1:没有使用requestAnimationFrame

<template>
  <div>
    <button @click="startAnimation">开始动画</button>
    <div ref="box" class="box"></div>
  </div>
</template>

<script>
export default {
  methods: {
    startAnimation() {
      const box = this.$refs.box
      let position = 0
      
      // 错误:使用setInterval进行动画
      const interval = setInterval(() => {
        position += 5
        box.style.transform = `translateX(${position}px)`
        
        if (position >= 300) {
          clearInterval(interval)
        }
      }, 16) // 约60fps
    }
  }
}
</script>

<style>
.box {
  width: 100px;
  height: 100px;
  background-color: red;
}
</style>

错误原因:setInterval的时间间隔不稳定,可能导致动画卡顿。

正确实现

<template>
  <div>
    <button @click="startAnimation">开始动画</button>
    <div ref="box" class="box"></div>
  </div>
</template>

<script>
export default {
  methods: {
    startAnimation() {
      const box = this.$refs.box
      let position = 0
      
      // 正确:使用requestAnimationFrame
      const animate = () => {
        position += 5
        box.style.transform = `translateX(${position}px)`
        
        if (position < 300) {
          requestAnimationFrame(animate)
        }
      }
      
      requestAnimationFrame(animate)
    }
  }
}
</script>

<style>
.box {
  width: 100px;
  height: 100px;
  background-color: red;
}
</style>

错误场景2:没有清理动画

<template>
  <div>
    <button @click="toggleAnimation">切换动画</button>
    <div ref="box" class="box"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isAnimating: false,
      animationId: null
    }
  },
  methods: {
    toggleAnimation() {
      if (this.isAnimating) {
        // 错误:没有清理动画
        this.isAnimating = false
      } else {
        this.isAnimating = true
        this.animate()
      }
    },
    animate() {
      const box = this.$refs.box
      let position = 0
      
      const animation = () => {
        if (!this.isAnimating) return
        
        position += 5
        box.style.transform = `translateX(${position}px)`
        
        if (position >= 300) {
          position = 0
        }
        
        this.animationId = requestAnimationFrame(animation)
      }
      
      this.animationId = requestAnimationFrame(animation)
    }
  }
}
</script>

<style>
.box {
  width: 100px;
  height: 100px;
  background-color: red;
}
</style>

错误原因:没有清理requestAnimationFrame,可能导致内存泄漏。

正确实现

<template>
  <div>
    <button @click="toggleAnimation">切换动画</button>
    <div ref="box" class="box"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isAnimating: false,
      animationId: null
    }
  },
  methods: {
    toggleAnimation() {
      if (this.isAnimating) {
        // 正确:清理动画
        this.isAnimating = false
        if (this.animationId) {
          cancelAnimationFrame(this.animationId)
        }
      } else {
        this.isAnimating = true
        this.animate()
      }
    },
    animate() {
      const box = this.$refs.box
      let position = 0
      
      const animation = () => {
        if (!this.isAnimating) return
        
        position += 5
        box.style.transform = `translateX(${position}px)`
        
        if (position >= 300) {
          position = 0
        }
        
        this.animationId = requestAnimationFrame(animation)
      }
      
      this.animationId = requestAnimationFrame(animation)
    }
  },
  beforeUnmount() {
    // 组件销毁时清理动画
    if (this.animationId) {
      cancelAnimationFrame(this.animationId)
    }
  }
}
</script>

<style>
.box {
  width: 100px;
  height: 100px;
  background-color: red;
}
</style>

10.6 Vue动画库的集成问题

核心知识点

  • 第三方动画库的集成方法
  • Vue与动画库的配合使用
  • 动画库的性能优化

常见错误场景

错误场景1:没有正确集成animate.css

<template>
  <div>
    <!-- 错误:直接使用animate.css类名 -->
    <transition name="animated">
      <div v-if="show" class="animated bounce">内容</div>
    </transition>
  </div>
</template>

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

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

错误原因:没有正确配置transition组件来使用animate.css。

正确实现

<template>
  <div>
    <!-- 正确:使用transition的enter-active-class和leave-active-class -->
    <transition
      enter-active-class="animate__animated animate__bounce"
      leave-active-class="animate__animated animate__fadeOut"
    >
      <div v-if="show">内容</div>
    </transition>
  </div>
</template>

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

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

错误场景2:过度使用动画库

<template>
  <div>
    <transition
      enter-active-class="animate__animated animate__bounce animate__rotateIn animate__pulse"
      leave-active-class="animate__animated animate__fadeOut"
    >
      <div v-if="show">内容</div>
    </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>

错误原因:同时使用多个动画效果会导致动画冲突和性能问题。

正确实现

<template>
  <div>
    <transition
      enter-active-class="animate__animated animate__bounce"
      leave-active-class="animate__animated animate__fadeOut"
    >
      <div v-if="show">内容</div>
    </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>

10.7 Vue路由过渡的常见错误

核心知识点

  • Vue Router的过渡效果
  • 路由过渡的配置
  • 嵌套路由的过渡处理

常见错误场景

错误场景1:路由过渡的基本配置错误

<template>
  <div>
    <!-- 错误:transition组件的位置不正确 -->
    <router-view v-slot="{ Component }">
      <transition name="fade">
        <Component />
      </transition>
    </router-view>
  </div>
</template>

<script>
export default {}
</script>

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

错误原因:Vue Router 3和Vue Router 4的路由过渡语法不同。

正确实现(Vue Router 4)

<template>
  <div>
    <!-- 正确:Vue Router 4的路由过渡语法 -->
    <router-view v-slot="{ Component }">
      <transition name="fade">
        <component :is="Component" />
      </transition>
    </router-view>
  </div>
</template>

<script>
export default {}
</script>

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

正确实现(Vue Router 3)

<template>
  <div>
    <!-- 正确:Vue Router 3的路由过渡语法 -->
    <transition name="fade">
      <router-view></router-view>
    </transition>
  </div>
</template>

<script>
export default {}
</script>

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

错误场景2:嵌套路由的过渡处理

<template>
  <div>
    <!-- 父路由过渡 -->
    <transition name="parent-fade">
      <router-view></router-view>
    </transition>
  </div>
</template>

<script>
export default {}
</script>

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

错误原因:没有考虑嵌套路由的过渡效果,可能导致动画冲突。

正确实现

<template>
  <div>
    <!-- 父路由过渡 -->
    <transition name="parent-fade">
      <router-view v-slot="{ Component }">
        <!-- 子路由过渡 -->
        <transition name="child-fade">
          <component :is="Component" />
        </transition>
      </router-view>
    </transition>
  </div>
</template>

<script>
export default {}
</script>

<style>
/* 父路由过渡 */
.parent-fade-enter-active,
.parent-fade-leave-active {
  transition: opacity 0.5s;
}
.parent-fade-enter-from,
.parent-fade-leave-to {
  opacity: 0;
}

/* 子路由过渡 */
.child-fade-enter-active,
.child-fade-leave-active {
  transition: transform 0.3s;
}
.child-fade-enter-from {
  transform: translateX(100px);
}
.child-fade-leave-to {
  transform: translateX(-100px);
}
</style>

10.8 Vue列表动画的使用误区

核心知识点

  • Vue列表动画的实现
  • transition-group组件的使用
  • 列表动画的性能优化

常见错误场景

错误场景1:使用transition包裹列表

<template>
  <div>
    <!-- 错误:使用transition包裹列表 -->
    <transition name="list">
      <div v-for="item in items" :key="item.id">
        {{ item.name }}
      </div>
    </transition>
    <button @click="addItem">添加项</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: '项1' },
        { id: 2, name: '项2' },
        { id: 3, name: '项3' }
      ]
    }
  },
  methods: {
    addItem() {
      this.items.push({ id: Date.now(), name: `项${this.items.length + 1}` })
    }
  }
}
</script>

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

错误原因:transition组件不能直接包裹列表,需要使用transition-group组件。

正确实现

<template>
  <div>
    <!-- 正确:使用transition-group包裹列表 -->
    <transition-group name="list" tag="div">
      <div v-for="item in items" :key="item.id">
        {{ item.name }}
      </div>
    </transition-group>
    <button @click="addItem">添加项</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: '项1' },
        { id: 2, name: '项2' },
        { id: 3, name: '项3' }
      ]
    }
  },
  methods: {
    addItem() {
      this.items.push({ id: Date.now(), name: `项${this.items.length + 1}` })
    }
  }
}
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
/* 列表项移动动画 */
.list-move {
  transition: transform 0.5s;
}
</style>

错误场景2:没有为列表项添加唯一的key

<template>
  <div>
    <transition-group name="list" tag="div">
      <!-- 错误:使用索引作为key -->
      <div v-for="(item, index) in items" :key="index">
        {{ item.name }}
        <button @click="removeItem(index)">删除</button>
      </div>
    </transition-group>
    <button @click="addItem">添加项</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { name: '项1' },
        { name: '项2' },
        { name: '项3' }
      ]
    }
  },
  methods: {
    addItem() {
      this.items.push({ name: `项${this.items.length + 1}` })
    },
    removeItem(index) {
      this.items.splice(index, 1)
    }
  }
}
</script>

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

错误原因:使用索引作为key会导致列表项的动画效果异常,特别是在删除操作时。

正确实现

<template>
  <div>
    <transition-group name="list" tag="div">
      <!-- 正确:使用唯一的id作为key -->
      <div v-for="item in items" :key="item.id">
        {{ item.name }}
        <button @click="removeItem(item.id)">删除</button>
      </div>
    </transition-group>
    <button @click="addItem">添加项</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { id: 1, name: '项1' },
        { id: 2, name: '项2' },
        { id: 3, name: '项3' }
      ]
    }
  },
  methods: {
    addItem() {
      this.items.push({ id: Date.now(), name: `项${this.items.length + 1}` })
    },
    removeItem(id) {
      this.items = this.items.filter(item => item.id !== id)
    }
  }
}
</script>

<style>
.list-enter-active,
.list-leave-active {
  transition: all 0.5s;
}
.list-enter-from,
.list-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
.list-move {
  transition: transform 0.5s;
}
</style>
« 上一篇 Vue表单处理踩坑 下一篇 » Vue测试踩坑