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>
  • 考虑可访问性和性能影响
相关推荐
JaceJufrog11 分钟前
VidGo-一个基于LLM的本地视频管理服务,集合字幕生成&工作流翻译&自定义合集&字幕编辑,导出
vue.js
console.log('npc')27 分钟前
前端性能优化,给录音播放的列表加个播放按键,点击之后再播放录音。减少页面的渲染录音文件数量过多导致加载缓慢
前端·javascript·vue.js·算法
努力往上爬de蜗牛1 小时前
文件下载 针对安卓系统
前端·javascript·vue.js
正义的大古1 小时前
OpenLayers地图交互 -- 章节十二:键盘平移交互详解
javascript·vue.js·openlayers
老华带你飞2 小时前
寝室快修|基于SprinBoot+vue的贵工程寝室快修小程序(源码+数据库+文档)
java·数据库·vue.js·spring boot·小程序·毕设·贵工程寝室快修
正义的大古2 小时前
OpenLayers地图交互 -- 章节十一:拖拽文件交互详解
javascript·vue.js·microsoft·openlayers
艾小码2 小时前
告别复制粘贴!掌握这7个原则,让你的Vue组件复用性翻倍
前端·javascript·vue.js
知识分享小能手11 小时前
React学习教程,从入门到精通,React 前后端交互技术详解(29)
前端·javascript·vue.js·学习·react.js·前端框架·react
许___11 小时前
基于 @antv/x6 实现流程图
vue.js·antv/x6
青柠编程14 小时前
基于 Spring Boot 与 Vue 的前后端分离课程答疑平台架构设计
vue.js·spring boot·后端