Vue 3 与 Framer Motion 高级应用

概述

Framer Motion 是一个现代化的 React 动画库,但也提供了 Vue 3 版本的支持。它以声明式的方式创建流畅的动画和交互效果,具有丰富的特性和良好的性能。本集将深入探讨 Vue 3 与 Framer Motion 的高级应用,包括动画变体、手势控制、滚动动画等。

核心知识点

1. Framer Motion 基础回顾

Framer Motion 的核心概念包括:

  • 动画组件motion.divmotion.button
  • 动画属性animateinitialexit
  • 过渡效果transition 属性
  • 动画变体variants 对象
  • 手势控制whileHoverwhileTap

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**:提示浏览器哪些属性将要变化
  • 限制动画属性:优先使用 transformopacity
  • 避免布局抖动:不要动画 widthheight 等会引起重排的属性
  • **使用 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. 动画性能差

问题:动画卡顿或不流畅

解决方案

  • 减少同时执行的动画数量
  • 优化动画属性,优先使用 transformopacity
  • 使用 will-change 属性
  • 考虑使用 hardwareAcceleration 选项

3. 页面过渡不工作

问题:路由切换时页面过渡效果不执行

解决方案

  • 确保使用了 :key=&quot;$route.path&quot;
  • 检查是否正确配置了 initialanimateexit 属性
  • 确保组件已正确导入 motion

4. 手势控制不响应

问题whileHoverwhileTap 不工作

解决方案

  • 确保元素有足够的尺寸和可点击区域
  • 检查是否有其他元素覆盖在上面
  • 确保没有 CSS pointer-events: none 属性

进阶学习资源

  1. 官方文档

  2. 视频教程

  3. 示例项目

  4. 社区资源

实践练习

练习1:创建一个带有动画变体的页面

要求

  • 创建一个包含标题、副标题、卡片列表和按钮的页面
  • 使用 Variants 定义动画序列
  • 实现页面加载时的依次动画效果
  • 为卡片添加悬停和点击效果

练习2:实现滚动触发的动画

要求

  • 创建一个长页面,包含多个内容区块
  • 实现以下滚动触发动画:
    • 元素进入视口时的淡入上移动画
    • 数字计数动画
    • 图片的缩放动画
    • 进度条动画

练习3:创建交互式动画组件

要求

  • 创建一个可复用的卡片组件,包含:
    • 悬停时的缩放和阴影效果
    • 点击时的按压效果
    • 图片加载时的淡入动画
    • 内容的渐显效果

练习4:实现视差滚动效果

要求

  • 创建一个视差滚动页面,包含:
    • 多层背景元素,以不同速度滚动
    • 前景内容的视差效果
    • 滚动时的文字动画
    • 响应式设计,适配不同设备

总结

Framer Motion 为 Vue 3 应用提供了强大的动画和交互能力,通过声明式的 API 和丰富的特性,可以轻松创建复杂、流畅的动画效果。掌握 Framer Motion 的高级特性,如动画变体、手势控制和滚动动画,可以极大地提升应用的用户体验。

在下一集中,我们将探讨 Vue 3 代码质量和可维护性,敬请期待!

« 上一篇 Vue 3 与 GSAP 深度集成:构建复杂动画效果 下一篇 » Vue 3 代码质量和可维护性:构建高质量应用