Vue 3 动画

一、动画组件与原理

1. <transition> 组件

用于单个元素的进入/离开动画,通过 CSS 类名或 JavaScript 钩子实现。

工作原理

  1. 当元素插入/移除时,Vue 会自动检测目标元素是否应用了 CSS 过渡/动画
  2. 在动画不同阶段添加/移除特定的 CSS 类名
  3. 如果检测到 JavaScript 钩子,则调用相应方法
html 复制代码
<button @click="show = !show">切换</button>
<transition name="fade">
  <p v-if="show">Hello Vue 3!</p>
</transition>

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

2. <transition-group> 组件

用于多个元素的列表过渡,特别处理元素位置变化。

html 复制代码
<transition-group name="list" tag="ul">
  <li v-for="item in items" :key="item.id">
    {{ item.text }}
  </li>
</transition-group>

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

二、CSS 过渡类名详解

Vue 会自动添加/移除以下类名(以 name="fade" 为例):

类名 添加时机 移除时机 用途
fade-enter-from 插入前 插入后下一帧 进入开始状态
fade-enter-active 整个进入阶段 过渡完成 进入激活状态
fade-enter-to enter-from 移除后 过渡完成 进入结束状态
fade-leave-from 离开触发时 下一帧 离开开始状态
fade-leave-active 整个离开阶段 过渡完成 离开激活状态
fade-leave-to leave-from 移除后 过渡完成 离开结束状态

三、JavaScript 钩子函数

html 复制代码
<transition
  @before-enter="onBeforeEnter"
  @enter="onEnter"
  @after-enter="onAfterEnter"
  @enter-cancelled="onEnterCancelled"
  @before-leave="onBeforeLeave"
  @leave="onLeave"
  @after-leave="onAfterLeave"
  @leave-cancelled="onLeaveCancelled"
>
  <!-- 元素 -->
</transition>

使用 GSAP 实现复杂动画

javascript 复制代码
import gsap from 'gsap'

export default {
  methods: {
    onEnter(el, done) {
      gsap.from(el, {
        duration: 0.8,
        opacity: 0,
        y: 50,
        rotation: 15,
        ease: 'back.out(1.7)',
        onComplete: done
      })
    },
    onLeave(el, done) {
      gsap.to(el, {
        duration: 0.6,
        opacity: 0,
        scale: 0.8,
        y: -30,
        ease: 'power2.in',
        onComplete: done
      })
    }
  }
}

四、常见场景与实现方案

1. 页面路由过渡

html 复制代码
<router-view v-slot="{ Component }">
  <transition name="page" mode="out-in">
    <component :is="Component" />
  </transition>
</router-view>

<style>
.page-enter-active, .page-leave-active {
  transition: all 0.4s cubic-bezier(0.68, -0.55, 0.27, 1.55);
}
.page-enter-from {
  opacity: 0;
  transform: translateX(50px);
}
.page-leave-to {
  opacity: 0;
  transform: translateX(-50px);
}
</style>

2. 模态框动画

html 复制代码
<transition name="modal">
  <div class="modal-wrapper" v-if="showModal">
    <div class="modal-content">
      <!-- 内容 -->
    </div>
  </div>
</transition>

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

.modal-enter-from, .modal-leave-to {
  opacity: 0;
  transform: scale(0.95);
}

.modal-content {
  transition: transform 0.3s ease;
}
</style>

3. 列表排序动画

html 复制代码
<transition-group 
  name="flip-list" 
  tag="div"
  class="item-container"
>
  <div v-for="item in items" :key="item.id" class="item">
    {{ item.name }}
  </div>
</transition-group>

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

4. 骨架屏加载动画

html 复制代码
<transition name="skeleton-fade" mode="out-in">
  <div v-if="loading" class="skeleton">
    <div class="skeleton-line"></div>
    <div class="skeleton-line"></div>
  </div>
  <div v-else>
    <!-- 实际内容 -->
  </div>
</transition>

<style>
.skeleton {
  animation: shimmer 1.5s infinite linear;
  background: linear-gradient(
    to right,
    #f0f0f0 8%,
    #e0e0e0 18%,
    #f0f0f0 33%
  );
  background-size: 800px 100px;
}

@keyframes shimmer {
  0% { background-position: -400px 0; }
  100% { background-position: 400px 0; }
}
</style>

5. 下拉菜单动画

javascript 复制代码
export default {
  methods: {
    beforeEnter(el) {
      el.style.height = '0';
    },
    enter(el, done) {
      const height = el.scrollHeight;
      el.style.transition = 'height 0.3s ease';
      el.style.height = height + 'px';
      
      el.addEventListener('transitionend', done);
    },
    leave(el, done) {
      el.style.height = el.scrollHeight + 'px';
      el.offsetHeight; // 触发重绘
      el.style.transition = 'height 0.3s ease';
      el.style.height = '0';
      
      el.addEventListener('transitionend', done);
    }
  }
}

五、与第三方动画库集成

1. Animate.css

html 复制代码
<transition
  enter-active-class="animate__animated animate__bounceIn"
  leave-active-class="animate__animated animate__bounceOut"
>
  <div v-if="show">内容</div>
</transition>

2. Motion One

javascript 复制代码
import { animate } from 'motion'

export default {
  methods: {
    enter(el) {
      animate(el, 
        { opacity: [0, 1], transform: ['translateY(20px)', 'none'] },
        { duration: 0.5, easing: 'ease-out' }
      )
    },
    leave(el, done) {
      animate(el, 
        { opacity: [1, 0], transform: ['none', 'scale(0.8)'] },
        { 
          duration: 0.4,
          easing: 'ease-in',
          complete: done 
        }
      )
    }
  }
}

六、高级动画技巧

1. 状态驱动的动画

vue 复制代码
<template>
  <div class="progress-bar">
    <div 
      class="progress-fill" 
      :style="{ width: progress + '%' }"
    ></div>
  </div>
</template>

<script>
import { gsap } from 'gsap'

export default {
  props: ['progress'],
  watch: {
    progress(newVal) {
      gsap.to(this.$el.querySelector('.progress-fill'), {
        duration: 0.8,
        width: `${newVal}%`,
        ease: 'power2.out'
      })
    }
  }
}
</script>

2. 可复用动画组件

vue 复制代码
<!-- SlideFade.vue -->
<template>
  <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @leave="leave"
    :css="false"
  >
    <slot></slot>
  </transition>
</template>

<script>
import gsap from 'gsap'

export default {
  props: {
    duration: {
      type: Number,
      default: 0.7
    }
  },
  methods: {
    beforeEnter(el) {
      gsap.set(el, {
        opacity: 0,
        y: 30
      })
    },
    enter(el, done) {
      gsap.to(el, {
        opacity: 1,
        y: 0,
        duration: this.duration,
        ease: 'elastic.out(1, 0.8)',
        onComplete: done
      })
    },
    leave(el, done) {
      gsap.to(el, {
        opacity: 0,
        y: -20,
        duration: this.duration * 0.7,
        ease: 'power2.in',
        onComplete: done
      })
    }
  }
}
</script>

<!-- 使用 -->
<SlideFade>
  <div v-if="show">内容</div>
</SlideFade>

七、性能优化与最佳实践

  1. 硬件加速

    css 复制代码
    .animated-element {
      will-change: transform, opacity;
      transform: translateZ(0);
    }
  2. 精简动画属性

    css 复制代码
    /* 优先使用 transform 和 opacity */
    transition: transform 0.4s, opacity 0.3s;
  3. 列表动画优化

    css 复制代码
    .list-item {
      position: relative;
      transition: all 0.5s;
    }
    .list-leave-active {
      position: absolute;
      width: 100%;
    }
  4. 减少动画元素数量

    html 复制代码
    <RecycleScroller>
      <transition-group>
        <!-- 长列表使用虚拟滚动 -->
      </transition-group>
    </RecycleScroller>
  5. 用户偏好设置

    css 复制代码
    @media (prefers-reduced-motion: reduce) {
      .transition-element {
        transition: none !important;
        animation: none !important;
      }
    }

八、常见问题解决

1. 初始渲染动画

html 复制代码
<transition
  appear
  appear-class="custom-appear"
  appear-to-class="custom-appear-to"
  appear-active-class="custom-appear-active"
>
  <!-- 元素 -->
</transition>

2. 动画与滚动位置冲突

javascript 复制代码
onLeave(el) {
  const scrollTop = document.documentElement.scrollTop
  // 执行动画...
  // 动画完成后恢复滚动位置
  requestAnimationFrame(() => {
    document.documentElement.scrollTop = scrollTop
  })
}

3. 动态高度过渡

javascript 复制代码
enter(el, done) {
  el.style.height = 'auto'
  const height = el.scrollHeight
  el.style.height = '0'
  
  requestAnimationFrame(() => {
    el.style.transition = 'height 0.4s ease'
    el.style.height = `${height}px`
    
    el.addEventListener('transitionend', done)
  })
}

九、总结

Vue 3 动画系统的核心要点:

  1. 基础组件<transition> 用于单个元素,<transition-group> 用于列表
  2. 实现方式:CSS 类名(简单场景)和 JavaScript 钩子(复杂动画)
  3. 关键技巧:模式选择、位置动画、可复用组件
  4. 性能优化:硬件加速、精简属性、虚拟滚动
  5. 场景覆盖:路由过渡、模态框、下拉菜单、骨架屏等

实际应用建议:

  • 简单动画使用 CSS 类名实现
  • 复杂交互使用 GSAP/Motion One 等库
  • 列表动画优先使用 <transition-group>
  • 考虑可访问性和性能影响
相关推荐
兮漫天1 小时前
bun + vite7 的结合,孕育的 Robot Admin 【靓仔出道】(十一)
前端·vue.js
前端开发爱好者2 小时前
Vite 7.1.1 疑似遭受大规模 "攻击"!
前端·vue.js·vite
cypking2 小时前
vue excel转json功能 xlsx
vue.js·json·excel
四岁爱上了她2 小时前
vue3使用插槽写一个自定义瀑布列表
前端·javascript·vue.js
har01d2 小时前
【CSS3】录音中。。。
前端·css·vue.js·vue·vue3·css3
wycode2 小时前
Vue2源码笔记(4)运行时-创建一个vue实例之initState数据劫持
前端·vue.js
wycode2 小时前
Vue2源码笔记(3)运行时-创建一个vue实例之init
前端·vue.js
前端小巷子4 小时前
Vue 2 生命周期全景剖析
前端·vue.js·面试
蚂蚁家的砖4 小时前
Vue 自定义水印指令实现方案解析
前端·javascript·vue.js