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>