一、动画组件与原理
1. <transition>
组件
用于单个元素的进入/离开动画,通过 CSS 类名或 JavaScript 钩子实现。
工作原理:
- 当元素插入/移除时,Vue 会自动检测目标元素是否应用了 CSS 过渡/动画
- 在动画不同阶段添加/移除特定的 CSS 类名
- 如果检测到 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>
七、性能优化与最佳实践
-
硬件加速:
css.animated-element { will-change: transform, opacity; transform: translateZ(0); }
-
精简动画属性:
css/* 优先使用 transform 和 opacity */ transition: transform 0.4s, opacity 0.3s;
-
列表动画优化:
css.list-item { position: relative; transition: all 0.5s; } .list-leave-active { position: absolute; width: 100%; }
-
减少动画元素数量:
html<RecycleScroller> <transition-group> <!-- 长列表使用虚拟滚动 --> </transition-group> </RecycleScroller>
-
用户偏好设置:
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 动画系统的核心要点:
- 基础组件 :
<transition>
用于单个元素,<transition-group>
用于列表 - 实现方式:CSS 类名(简单场景)和 JavaScript 钩子(复杂动画)
- 关键技巧:模式选择、位置动画、可复用组件
- 性能优化:硬件加速、精简属性、虚拟滚动
- 场景覆盖:路由过渡、模态框、下拉菜单、骨架屏等
实际应用建议:
- 简单动画使用 CSS 类名实现
- 复杂交互使用 GSAP/Motion One 等库
- 列表动画优先使用
<transition-group>
- 考虑可访问性和性能影响