Vue 3 高级动画和交互
概述
动画和交互是现代Web应用的重要组成部分,能够提升用户体验和界面吸引力。Vue 3提供了强大的过渡系统,包括Transition和TransitionGroup组件,支持CSS过渡、CSS动画和JavaScript钩子函数。本教程将深入探讨Vue 3中高级动画和交互的实现方法,包括复杂过渡效果、自定义动画、性能优化等内容,帮助你构建流畅、吸引人的用户界面。
核心知识
1. Vue 3 过渡系统
1.1 Transition 组件
Transition组件用于单个元素或组件的进入和离开过渡:
<template>
<button @click="show = !show">切换显示</button>
<Transition name="fade">
<div v-if="show" class="box">
过渡元素
</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.5s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
.box {
width: 200px;
height: 200px;
background-color: #42b883;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 18px;
}
</style>1.2 TransitionGroup 组件
TransitionGroup组件用于列表的进入、离开和移动过渡:
<template>
<button @click="addItem">添加项目</button>
<button @click="removeItem">移除项目</button>
<TransitionGroup name="list" tag="ul">
<li
v-for="item in items"
:key="item.id"
class="list-item"
>
{{ item.text }}
</li>
</TransitionGroup>
</template>
<script setup>
import { ref } from 'vue'
const items = ref([
{ id: 1, text: '项目 1' },
{ id: 2, text: '项目 2' },
{ id: 3, text: '项目 3' }
])
let nextId = 4
const addItem = () => {
items.value.push({ id: nextId++, text: `项目 ${nextId - 1}` })
}
const removeItem = () => {
items.value.pop()
}
</script>
<style scoped>
.list-item {
width: 200px;
height: 50px;
background-color: #42b883;
border-radius: 4px;
margin: 8px 0;
display: flex;
align-items: center;
justify-content: center;
color: white;
}
.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;
}
ul {
list-style: none;
padding: 0;
margin: 0;
}
</style>2. 自定义过渡类名
2.1 自定义过渡类
可以使用自定义过渡类名覆盖默认类名,便于集成第三方动画库:
<template>
<Transition
enter-active-class="animate__animated animate__bounceIn"
leave-active-class="animate__animated animate__bounceOut"
>
<div v-if="show" class="box">
Animate.css 动画
</div>
</Transition>
</template>2.2 动态过渡类
根据条件动态切换过渡效果:
<template>
<select v-model="transitionName">
<option value="fade">淡入淡出</option>
<option value="slide">滑动</option>
<option value="scale">缩放</option>
</select>
<Transition :name="transitionName">
<div v-if="show" class="box">
动态过渡效果
</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
const transitionName = ref('fade')
</script>3. 动画钩子函数
3.1 JavaScript 钩子
使用JavaScript钩子函数实现复杂的动画逻辑:
<template>
<Transition
@before-enter="beforeEnter"
@enter="enter"
@after-enter="afterEnter"
@enter-cancelled="enterCancelled"
@before-leave="beforeLeave"
@leave="leave"
@after-leave="afterLeave"
@leave-cancelled="leaveCancelled"
>
<div v-if="show" ref="el" class="box">
JavaScript 动画
</div>
</Transition>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
const el = ref(null)
const beforeEnter = (el) => {
el.style.opacity = 0
el.style.transform = 'scale(0)'
}
const enter = (el, done) => {
gsap.to(el, {
opacity: 1,
transform: 'scale(1)',
duration: 0.5,
onComplete: done
})
}
const afterEnter = (el) => {
console.log('进入动画完成')
}
const enterCancelled = (el) => {
console.log('进入动画取消')
}
// 离开动画钩子类似...
</script>3.2 结合 CSS 和 JavaScript 动画
可以同时使用CSS和JavaScript动画:
<template>
<Transition
name="css-animation"
@enter="enter"
@leave="leave"
>
<div v-if="show" class="box">
混合动画
</div>
</Transition>
</template>
<style scoped>
.css-animation-enter-active,
.css-animation-leave-active {
transition: background-color 0.5s ease;
}
.css-animation-enter-from,
.css-animation-leave-to {
background-color: #ff7875;
}
</style>4. 高级过渡效果
4.1 交错动画
实现列表项的交错动画:
<template>
<button @click="toggleList">切换列表</button>
<TransitionGroup
name="stagger"
tag="ul"
@before-enter="beforeEnter"
@enter="enter"
@leave="leave"
>
<li
v-for="(item, index) in items"
:key="item.id"
:data-index="index"
class="stagger-item"
>
{{ item.text }}
</li>
</TransitionGroup>
</template>
<script setup>
import { ref } from 'vue'
const show = ref(false)
const items = ref([
{ id: 1, text: '项目 1' },
{ id: 2, text: '项目 2' },
{ id: 3, text: '项目 3' },
{ id: 4, text: '项目 4' },
{ id: 5, text: '项目 5' }
])
const toggleList = () => {
show.value = !show.value
}
const beforeEnter = (el) => {
el.style.opacity = 0
el.style.transform = 'translateY(20px)'
}
const enter = (el, done) => {
const index = el.dataset.index
gsap.to(el, {
opacity: 1,
transform: 'translateY(0)',
duration: 0.5,
delay: index * 0.1,
onComplete: done
})
}
const leave = (el, done) => {
const index = el.dataset.index
gsap.to(el, {
opacity: 0,
transform: 'translateY(20px)',
duration: 0.5,
delay: index * 0.1,
onComplete: done
})
}
</script>4.2 状态驱动的动画
使用计算属性和watch实现状态驱动的动画:
<template>
<input
type="range"
v-model="progress"
min="0"
max="100"
>
<div
class="progress-bar"
:style="{ width: `${progress}%` }"
></div>
</template>
<script setup>
import { ref, watch } from 'vue'
const progress = ref(0)
// 使用 watch 监听状态变化,实现动画
watch(progress, (newValue, oldValue) => {
// 可以在这里添加动画逻辑
console.log('进度变化:', oldValue, '->', newValue)
})
</script>
<style scoped>
.progress-bar {
height: 20px;
background-color: #42b883;
transition: width 0.3s ease;
border-radius: 10px;
margin-top: 20px;
}
</style>4.3 时间线动画
使用GSAP等库创建复杂的时间线动画:
<template>
<button @click="playAnimation">播放动画</button>
<div class="animation-container">
<div ref="box1" class="box box1">盒子 1</div>
<div ref="box2" class="box box2">盒子 2</div>
<div ref="box3" class="box box3">盒子 3</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import gsap from 'gsap'
const box1 = ref(null)
const box2 = ref(null)
const box3 = ref(null)
const playAnimation = () => {
// 创建时间线
const tl = gsap.timeline()
// 添加动画序列
tl.to(box1.value, {
x: 200,
duration: 1,
ease: 'power2.out'
})
.to(box2.value, {
y: -100,
duration: 0.8,
ease: 'bounce.out'
}, '-=0.5') // 与前一个动画重叠 0.5 秒
.to(box3.value, {
scale: 1.5,
duration: 0.6,
ease: 'elastic.out(1, 0.3)'
})
.to([box1.value, box2.value, box3.value], {
opacity: 0.5,
duration: 0.5
})
.to([box1.value, box2.value, box3.value], {
opacity: 1,
duration: 0.5
})
}
</script>
<style scoped>
.animation-container {
position: relative;
width: 400px;
height: 300px;
margin-top: 20px;
background-color: #f0f0f0;
border-radius: 8px;
overflow: hidden;
}
.box {
position: absolute;
width: 80px;
height: 80px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
.box1 {
background-color: #42b883;
top: 20px;
left: 20px;
}
.box2 {
background-color: #35495e;
top: 20px;
right: 20px;
}
.box3 {
background-color: #ff7875;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
</style>5. 性能优化
5.1 使用 will-change
使用 will-change 属性提示浏览器优化动画:
.animated-element {
will-change: transform, opacity;
transition: transform 0.3s ease, opacity 0.3s ease;
}5.2 避免布局抖动
使用 transform 和 opacity 属性进行动画,避免触发布局重排:
/* 推荐:只使用 transform 和 opacity */
.good-animation {
transition: transform 0.3s ease, opacity 0.3s ease;
}
/* 不推荐:使用可能触发布局重排的属性 */
.bad-animation {
transition: width 0.3s ease, height 0.3s ease, margin 0.3s ease;
}5.3 动画节流
对频繁触发的动画进行节流处理:
<template>
<div
class="drag-area"
@mousemove="handleMouseMove"
>
<div
class="drag-box"
:style="{ left: x + 'px', top: y + 'px' }"
></div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const x = ref(0)
const y = ref(0)
let isDragging = false
// 使用节流优化鼠标移动事件
let timeoutId = null
const handleMouseMove = (event) => {
if (timeoutId) {
clearTimeout(timeoutId)
}
timeoutId = setTimeout(() => {
x.value = event.offsetX
y.value = event.offsetY
}, 16) // 约 60fps
}
</script>5.4 使用 CSS 变量
使用 CSS 变量实现动态样式和动画:
<template>
<input
type="range"
v-model="hue"
min="0"
max="360"
>
<div class="css-vars-box" :style="{ '--hue': hue }"></div>
</template>
<script setup>
import { ref } from 'vue'
const hue = ref(120)
</script>
<style scoped>
.css-vars-box {
width: 200px;
height: 200px;
background-color: hsl(var(--hue, 120), 70%, 60%);
transition: background-color 0.3s ease;
border-radius: 8px;
margin-top: 20px;
}
</style>最佳实践
1. 选择合适的动画类型
- CSS 过渡:适合简单的状态变化(如显示/隐藏、悬停效果)
- CSS 动画:适合复杂的循环动画
- JavaScript 动画:适合需要交互或复杂逻辑的动画
- 第三方库:适合复杂的时间线动画和特效
2. 保持动画简洁
- 避免过度使用动画,以免分散用户注意力
- 保持动画时长适当(通常 200-500ms)
- 使用一致的缓动函数
3. 考虑可访问性
- 为动画添加
prefers-reduced-motion支持 - 允许用户禁用动画
- 确保动画不会导致视觉疲劳
/* 尊重用户的动画偏好 */
@media (prefers-reduced-motion: reduce) {
* {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
}
}4. 性能优化
- 使用
transform和opacity进行动画 - 使用
will-change提示浏览器 - 避免在动画期间进行布局操作
- 对频繁触发的动画进行节流或防抖
5. 组织动画代码
- 将动画逻辑与业务逻辑分离
- 使用组合式函数封装动画逻辑
- 为复杂动画创建专门的组件
6. 测试动画
- 在不同设备上测试动画性能
- 使用浏览器开发者工具分析动画性能
- 测试动画对电池寿命的影响
常见问题与解决方案
1. 问题:动画在某些浏览器上不流畅
解决方案:
- 使用
transform和opacity属性 - 添加
will-change属性 - 简化动画复杂度
- 使用硬件加速
2. 问题:动画触发布局抖动
解决方案:
- 避免使用影响布局的属性(width, height, margin, padding)
- 使用
transform进行位置和大小变换 - 使用
opacity进行透明度变化
3. 问题:JavaScript 动画不执行
解决方案:
- 确保调用了
done回调函数 - 检查动画库是否正确导入
- 确保 DOM 元素已挂载
- 检查浏览器控制台是否有错误
4. 问题:列表动画中项目位置计算错误
解决方案:
- 确保所有列表项有唯一的
key - 使用固定尺寸的列表项
- 避免动态改变列表项的尺寸
5. 问题:动画与过渡冲突
解决方案:
- 避免同时使用 CSS 过渡和 JavaScript 动画
- 确保动画钩子函数正确处理
- 合理设置动画优先级
高级学习资源
1. 官方文档
2. 工具和库
- GSAP:功能强大的JavaScript动画库
- Animate.css:现成的CSS动画库
- Framer Motion:React生态中流行的动画库,也支持Vue
- Motion One:轻量级的动画库,支持Web Animations API
3. 性能分析工具
- Chrome DevTools Performance 面板
- Firefox DevTools Performance 面板
- Lighthouse:网站性能分析工具
4. 最佳实践指南
实践练习
练习 1:复杂过渡效果
- 创建一个带有多个元素的组件
- 实现元素的进入、离开和移动过渡
- 使用自定义过渡类名
- 添加动画钩子函数
练习 2:交错动画
- 创建一个列表组件
- 实现列表项的交错进入动画
- 实现列表项的交错离开动画
- 测试不同的延迟和缓动函数
练习 3:状态驱动的动画
- 创建一个带有滑块的组件
- 使用计算属性和watch实现状态驱动的动画
- 测试不同的过渡效果
- 优化动画性能
练习 4:时间线动画
- 使用GSAP创建一个复杂的时间线动画
- 添加多个动画序列
- 实现动画的重叠和延迟
- 添加动画控制(播放、暂停、重播)
练习 5:交互驱动的动画
- 创建一个拖拽组件
- 实现拖拽过程中的动画效果
- 添加拖拽结束后的惯性动画
- 优化拖拽性能
总结
Vue 3提供了强大的过渡系统,支持CSS过渡、CSS动画和JavaScript钩子函数,能够实现各种复杂的动画效果。通过结合第三方动画库(如GSAP),可以创建更加流畅、吸引人的用户界面。
本教程介绍了Vue 3中高级动画和交互的核心概念、实现方法和最佳实践,包括Transition和TransitionGroup组件、自定义过渡类名、动画钩子函数、状态驱动的动画、时间线动画等内容。通过学习这些内容,你可以构建出流畅、高性能的动画效果,提升用户体验。
在实际开发中,你需要根据具体需求选择合适的动画类型,保持动画简洁,考虑可访问性,并进行性能优化。同时,你还需要测试动画在不同设备上的表现,确保动画流畅且不影响用户体验。