Vue 3 与 Framer Motion 高级应用
概述
Framer Motion 是一个现代化的 React 动画库,但也提供了 Vue 3 版本的支持。它以声明式的方式创建流畅的动画和交互效果,具有丰富的特性和良好的性能。本集将深入探讨 Vue 3 与 Framer Motion 的高级应用,包括动画变体、手势控制、滚动动画等。
核心知识点
1. Framer Motion 基础回顾
Framer Motion 的核心概念包括:
- 动画组件:
motion.div、motion.button等 - 动画属性:
animate、initial、exit等 - 过渡效果:
transition属性 - 动画变体:
variants对象 - 手势控制:
whileHover、whileTap等
2. 在 Vue 3 中集成 Framer Motion
安装 Framer Motion for Vue
npm install framer-motion-vue基本使用
<template>
<motion.div
class="box"
initial={{ opacity: 0, scale: 0.5 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.5 }}
/>
</template>
<script setup>
import { motion } from 'framer-motion-vue'
</script>3. 动画变体(Variants)
Variants 允许你定义可复用的动画配置,实现更复杂的动画序列。
基础变体
<template>
<motion.div
class="box"
:initial="variants.hidden"
:animate="variants.visible"
:variants="variants"
/>
</template>
<script setup>
import { motion } from 'framer-motion-vue'
const variants = {
hidden: {
opacity: 0,
scale: 0.5
},
visible: {
opacity: 1,
scale: 1,
transition: {
duration: 0.5
}
}
}
</script>复杂变体与动画序列
<template>
<div class="container">
<motion.h1 :variants="containerVariants" :initial="'hidden'" :animate="'visible'">
标题
</motion.h1>
<motion.div
class="box"
:variants="containerVariants"
:initial="'hidden'"
:animate="'visible'"
/>
<motion.button
class="button"
:variants="containerVariants"
:initial="'hidden'"
:animate="'visible'"
>
按钮
</motion.button>
</div>
</template>
<script setup>
import { motion } from 'framer-motion-vue'
const containerVariants = {
hidden: {
opacity: 0
},
visible: {
opacity: 1,
transition: {
staggerChildren: 0.2 // 子元素依次动画,间隔 0.2s
}
}
}
</script>4. 过渡与动画控制
自定义过渡
<template>
<motion.div
class="box"
:animate="{ x: isVisible ? 100 : 0 }"
:transition="{
type: 'spring',
stiffness: 500,
damping: 30
}"
/>
<button @click="isVisible = !isVisible">切换</button>
</template>
<script setup>
import { ref } from 'vue'
import { motion } from 'framer-motion-vue'
const isVisible = ref(false)
</script>不同过渡类型
// 弹簧动画
transition: {
type: 'spring',
stiffness: 100,
damping: 10
}
// 淡入淡出
transition: {
type: 'tween',
duration: 1,
ease: 'easeInOut'
}
// 弹性动画
transition: {
type: 'spring',
bounce: 0.5
}5. 手势控制
Framer Motion 提供了丰富的手势控制属性:
悬停与点击效果
<template>
<motion.button
class="button"
whileHover={{ scale: 1.1, backgroundColor: '#42b883' }}
whileTap={{ scale: 0.95 }}
>
点击我
</motion.button>
</template>
<script setup>
import { motion } from 'framer-motion-vue'
</script>拖拽功能
<template>
<motion.div
class="box"
draggable
whileDrag={{ scale: 1.2, cursor: 'grabbing' }}
onDragStart={() => console.log('拖拽开始')}
onDragEnd={() => console.log('拖拽结束')}
/>
</template>
<script setup>
import { motion } from 'framer-motion-vue'
</script>6. 页面过渡与路由集成
结合 Vue Router 实现页面过渡效果:
<!-- App.vue -->
<template>
<RouterView v-slot="{ Component }">
<motion.div
:key="$route.path"
initial="pageInitial"
animate="pageAnimate"
exit="pageExit"
:variants="pageVariants"
>
<component :is="Component" />
</motion.div>
</RouterView>
</template>
<script setup>
import { motion } from 'framer-motion-vue'
const pageVariants = {
pageInitial: {
opacity: 0,
y: 20
},
pageAnimate: {
opacity: 1,
y: 0,
transition: {
duration: 0.5
}
},
pageExit: {
opacity: 0,
y: -20,
transition: {
duration: 0.3
}
}
}
</script>7. 滚动动画
Framer Motion 提供了 useScroll hook 用于创建基于滚动的动画:
<template>
<div class="scroll-container">
<motion.div
class="animate-element"
:style="{
opacity: opacity,
y: y
}"
>
滚动动画
</motion.div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import { motion, useScroll } from 'framer-motion-vue'
const opacity = ref(0)
const y = ref(50)
onMounted(() => {
const { scrollYProgress } = useScroll()
// 监听滚动进度,范围 0-1
scrollYProgress.onChange((latest) => {
opacity.value = latest
y.value = 50 - (latest * 50)
})
})
</script>8. 高级动画技巧
视差滚动效果
<template>
<div class="parallax-container">
<motion.div
class="parallax-bg"
:style="{
y: y * 0.5 // 背景移动速度较慢,产生视差效果
}"
/>
<motion.div
class="parallax-content"
:style="{
y: y * 0.2
}"
>
<h2>视差滚动</h2>
<p>这是一个视差滚动效果</p>
</motion.div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { motion, useScroll } from 'framer-motion-vue'
const y = ref(0)
const { scrollY } = useScroll()
scrollY.onChange((latest) => {
y.value = latest
})
</script>滚动触发动画
<template>
<div class="scroll-container">
<div class="section"></div>
<motion.div
ref="targetRef"
class="animate-element"
initial={{ opacity: 0, x: -100 }}
whileInView={{ opacity: 1, x: 0 }}
viewport={{ once: true, amount: 0.2 }}
transition={{ duration: 0.8 }}
>
滚动到此处显示动画
</motion.div>
<div class="section"></div>
</div>
</template>
<script setup>
import { ref } from 'vue'
import { motion } from 'framer-motion-vue'
const targetRef = ref(null)
</script>9. 创建可复用的动画组件
封装可复用的动画组件:
<!-- AnimatedButton.vue -->
<template>
<motion.button
class="button"
:whileHover="hoverVariants"
:whileTap="tapVariants"
:initial="initialVariants"
:animate="animateVariants"
:variants="variants"
@click="$emit('click')"
>
<slot></slot>
</motion.button>
</template>
<script setup>
import { motion } from 'framer-motion-vue'
const props = defineProps({
initialVariants: {
type: Object,
default: () => ({})
},
animateVariants: {
type: Object,
default: () => ({})
}
})
defineEmits(['click'])
const hoverVariants = {
scale: 1.05,
backgroundColor: '#42b883',
transition: {
duration: 0.2
}
}
const tapVariants = {
scale: 0.95
}
const variants = {
// 可以在这里定义更多变体
}
</script>使用可复用组件:
<template>
<AnimatedButton @click="handleClick">
自定义按钮
</AnimatedButton>
</template>
<script setup>
import AnimatedButton from './AnimatedButton.vue'
const handleClick = () => {
console.log('按钮被点击')
}
</script>最佳实践
1. 性能优化
- **使用
will-change**:提示浏览器哪些属性将要变化 - 限制动画属性:优先使用
transform和opacity - 避免布局抖动:不要动画
width、height等会引起重排的属性 - **使用
layoutId**:实现元素在不同组件间的平滑过渡 - **合理使用
once: true**:对于只需要执行一次的动画
2. 动画设计
- 保持一致性:在整个应用中使用统一的动画风格
- 适度使用动画:不要过度使用动画,避免干扰用户体验
- 考虑可访问性:提供动画开关,尊重系统动画偏好
- 测试不同设备:确保动画在各种设备上都能正常工作
3. 代码组织
- 使用 Variants:将动画配置集中管理,提高可维护性
- 封装可复用组件:将常用动画效果封装为组件
- 分离动画逻辑:将动画相关代码与业务逻辑分离
- 使用 Composables:封装复杂的动画逻辑
4. 可访问性
- **尊重
prefers-reduced-motion**:
import { useReducedMotion } from 'framer-motion-vue'
const shouldReduceMotion = useReducedMotion()
const transition = shouldReduceMotion ? {
duration: 0
} : {
duration: 0.5
}- 提供替代方案:对于依赖动画的交互,提供非动画替代方案
- 确保内容可读性:动画不要影响内容的可读性
常见问题与解决方案
1. 动画不执行
问题:Framer Motion 动画没有执行
解决方案:
- 确保已正确安装
framer-motion-vue - 检查组件是否正确导入
motion - 确保动画属性名称拼写正确
- 检查是否有 CSS 冲突
2. 动画性能差
问题:动画卡顿或不流畅
解决方案:
- 减少同时执行的动画数量
- 优化动画属性,优先使用
transform和opacity - 使用
will-change属性 - 考虑使用
hardwareAcceleration选项
3. 页面过渡不工作
问题:路由切换时页面过渡效果不执行
解决方案:
- 确保使用了
:key="$route.path" - 检查是否正确配置了
initial、animate和exit属性 - 确保组件已正确导入
motion
4. 手势控制不响应
问题:whileHover 或 whileTap 不工作
解决方案:
- 确保元素有足够的尺寸和可点击区域
- 检查是否有其他元素覆盖在上面
- 确保没有 CSS
pointer-events: none属性
进阶学习资源
官方文档:
视频教程:
示例项目:
社区资源:
实践练习
练习1:创建一个带有动画变体的页面
要求:
- 创建一个包含标题、副标题、卡片列表和按钮的页面
- 使用 Variants 定义动画序列
- 实现页面加载时的依次动画效果
- 为卡片添加悬停和点击效果
练习2:实现滚动触发的动画
要求:
- 创建一个长页面,包含多个内容区块
- 实现以下滚动触发动画:
- 元素进入视口时的淡入上移动画
- 数字计数动画
- 图片的缩放动画
- 进度条动画
练习3:创建交互式动画组件
要求:
- 创建一个可复用的卡片组件,包含:
- 悬停时的缩放和阴影效果
- 点击时的按压效果
- 图片加载时的淡入动画
- 内容的渐显效果
练习4:实现视差滚动效果
要求:
- 创建一个视差滚动页面,包含:
- 多层背景元素,以不同速度滚动
- 前景内容的视差效果
- 滚动时的文字动画
- 响应式设计,适配不同设备
总结
Framer Motion 为 Vue 3 应用提供了强大的动画和交互能力,通过声明式的 API 和丰富的特性,可以轻松创建复杂、流畅的动画效果。掌握 Framer Motion 的高级特性,如动画变体、手势控制和滚动动画,可以极大地提升应用的用户体验。
在下一集中,我们将探讨 Vue 3 代码质量和可维护性,敬请期待!