概述
Motion 是一个现代化的动画库,专为 Vue3 设计,提供了 motion-v 包。它结合了硬件加速的浏览器 API 性能和 JavaScript 的灵活性,为 Vue 应用提供流畅、高性能的动画体验。
核心优势:
- 🚀 硬件加速性能
- 🎨 丰富的动画类型
- 📱 完整的手势支持
- 🔄 智能布局动画
- ⚡ 简洁的 API 设计
- 🛠️ 强大的自定义能力
安装
npm install motion-v
基础运用
卡片悬停动画
<template>
<div class="flex justify-center p-12">
<motion.div
:whileHover="{
scale: 1.05,
rotateY: 5,
boxShadow: '0 20px 40px rgba(0,0,0,0.1)',
}"
:whileTap="{ scale: 0.95 }"
:transition="{ type: 'spring', stiffness: 300, damping: 20 }"
class="w-80 p-8 bg-gradient-to-br from-indigo-500 to-purple-600 rounded-2xl text-white text-center cursor-pointer"
style="transform-style: preserve-3d;"
>
<p class="m-0 opacity-90 leading-relaxed">
流畅的悬停效果,带来出色的用户体验
</p>
</motion.div>
</div>
</template>
<script setup>
import { motion } from "motion-v";
</script>
拖拽功能
<template>
<motion.div
:drag="true"
:dragConstraints="{ left: 0, right: 300 }"
class="w-24 h-24 bg-gradient-to-br from-red-400 to-teal-400 rounded-lg cursor-grab"
>
可拖拽元素
</motion.div>
</template>
<script setup>
import { motion } from "motion-v";
</script>
高级动画效果
数字计数动画
<template>
<div
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8 p-20 bg-slate-50"
>
<motion.div
v-for="stat in stats"
:key="stat.label"
:initial="{ opacity: 0, scale: 0.5 }"
:whileInView="{ opacity: 1, scale: 1 }"
:transition="{ duration: 0.5, delay: stat.delay }"
class="text-center p-10 bg-white rounded-2xl shadow-xl transition-transform duration-300 hover:-translate-y-2"
>
<motion.div
:initial="{ scale: 0 }"
:whileInView="{ scale: 1 }"
:transition="{
delay: stat.delay + 0.2,
type: 'spring',
stiffness: 200,
}"
class="text-5xl mb-5"
>
{{ stat.icon }}
</motion.div>
<motion.div
:initial="{ opacity: 0 }"
:whileInView="{ opacity: 1 }"
:transition="{ delay: stat.delay + 0.4 }"
class="text-5xl font-bold text-blue-700 mb-2"
>
{{ animatedValue(stat.value) }}
</motion.div>
<motion.div
:initial="{ opacity: 0, y: 20 }"
:whileInView="{ opacity: 1, y: 0 }"
:transition="{ delay: stat.delay + 0.6 }"
class="text-lg text-slate-600 font-medium"
>
{{ stat.label }}
</motion.div>
</motion.div>
</div>
</template>
<script setup>
import { motion, useInView } from "motion-v";
import { ref, onMounted } from "vue";
const stats = ref([
{ icon: "👥", value: 10000, label: "用户数量", delay: 0 },
{ icon: "⭐", value: 4.9, label: "用户评分", delay: 0.1 },
{ icon: "🚀", value: 99.9, label: "运行时间", delay: 0.2 },
{ icon: "💎", value: 50, label: "功能特性", delay: 0.3 },
]);
const animatedValues = ref({});
const animateNumber = (target, key, duration = 2000) => {
const start = 0;
const increment = target / (duration / 16);
let current = start;
const timer = setInterval(() => {
current += increment;
if (current >= target) {
current = target;
clearInterval(timer);
}
animatedValues.value[key] = current.toFixed(key.includes(".") ? 1 : 0);
}, 16);
};
const animatedValue = (value) => {
return animatedValues.value[value] || 0;
};
onMounted(() => {
stats.value.forEach((stat) => {
animateNumber(stat.value, stat.value.toString());
});
});
</script>

粒子动画背景
<template>
<div
class="relative min-h-screen bg-gradient-to-br from-blue-900 to-blue-700 overflow-hidden"
>
<motion.div
v-for="(particle, index) in particles"
:key="index"
:initial="{
x: particle.initialX,
y: particle.initialY,
scale: 0,
}"
:animate="{
x: particle.x,
y: particle.y,
scale: [0, 1, 0],
rotate: 360,
}"
:transition="{
duration: particle.duration,
repeat: Infinity,
ease: 'linear',
}"
class="absolute rounded-full opacity-70"
:style="{
background: particle.color,
width: particle.size + 'px',
height: particle.size + 'px',
}"
/>
<div
class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 text-center text-white z-10"
>
<motion.h1
:initial="{ opacity: 0, y: 50 }"
:animate="{ opacity: 1, y: 0 }"
:transition="{ duration: 1 }"
class="text-6xl font-bold mb-5 bg-gradient-to-r from-white to-cyan-200 bg-clip-text text-transparent"
>
粒子动画背景
</motion.h1>
<motion.p
:initial="{ opacity: 0, y: 30 }"
:animate="{ opacity: 1, y: 0 }"
:transition="{ delay: 0.5, duration: 1 }"
class="text-2xl opacity-90"
>
动态粒子效果,营造科技感氛围
</motion.p>
</div>
</div>
</template>
<script setup>
import { motion } from "motion-v";
import { ref, onMounted } from "vue";
const particles = ref([]);
const createParticles = () => {
const colors = [
"#ff6b6b",
"#4ecdc4",
"#45b7d1",
"#96ceb4",
"#feca57",
"#ff9ff3",
];
// 获取窗口尺寸,如果window不存在则使用默认值
const width = typeof window !== "undefined" ? window.innerWidth : 1200;
const height = typeof window !== "undefined" ? window.innerHeight : 800;
for (let i = 0; i < 50; i++) {
particles.value.push({
x: Math.random() * width,
y: Math.random() * height,
initialX: Math.random() * width,
initialY: Math.random() * height,
size: Math.random() * 4 + 2,
color: colors[Math.floor(Math.random() * colors.length)],
duration: Math.random() * 10 + 5,
});
}
};
onMounted(() => {
createParticles();
});
</script>

列表重排动画
<template>
<div class="max-w-2xl mx-auto my-12 px-5">
<motion.div layout>
<motion.div
v-for="item in items"
:key="item.id"
layout
:layoutId="`item-${item.id}`"
:whileHover="{ scale: 1.05, boxShadow: '0 10px 30px rgba(0,0,0,0.2)' }"
:whileTap="{ scale: 0.95 }"
class="bg-white rounded-2xl my-4 cursor-pointer overflow-hidden shadow-lg transition-all duration-300"
@click="removeItem(item.id)"
>
<div class="flex items-center p-5">
<div class="text-3xl mr-5">{{ item.icon }}</div>
<div class="flex-1">
<h3 class="m-0 mb-1 text-xl text-blue-700">{{ item.name }}</h3>
<p class="m-0 text-slate-600 text-sm">{{ item.description }}</p>
</div>
<div class="ml-5">
<button
class="w-8 h-8 rounded-full border-0 bg-red-500 text-white text-xl cursor-pointer flex items-center justify-center transition-colors duration-300 hover:bg-red-600"
>
×
</button>
</div>
</div>
</motion.div>
</motion.div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { motion } from "motion-v";
const items = ref([
{ id: 1, name: "项目 1", description: "这是一个很棒的项目", icon: "🚀" },
{ id: 2, name: "项目 2", description: "具有创新性的解决方案", icon: "💡" },
{ id: 3, name: "项目 3", description: "用户体验极佳的产品", icon: "⭐" },
{ id: 4, name: "项目 4", description: "技术领先的应用程序", icon: "⚡" },
{ id: 5, name: "项目 5", description: "安全可靠的企业方案", icon: "🔒" },
]);
const removeItem = (id) => {
const index = items.value.findIndex((item) => item.id === id);
if (index > -1) {
items.value.splice(index, 1);
}
};
</script>

性能优化建议
使用 will-change 属性
<template>
<motion.div
:animate="{ x: 100, scale: 1.2 }"
:style="{ willChange: 'transform' }"
>
优化性能的动画
</motion.div>
</template>
使用 transform 和 opacity
<template>
<motion.div
:animate="{
x: 100, // ✅ 使用 transform
scale: 1.2, // ✅ 使用 transform
opacity: 0.8, // ✅ 使用 opacity
// backgroundColor: 'red' // ❌ 避免重绘属性
}"
>
最佳性能实践
</motion.div>
</template>
总结
Motion 模块为 Vue3 提供了强大而简洁的动画解决方案。通过声明式 API,开发者可以轻松实现复杂的动画效果,包括手势交互、滚动动画、布局动画等。
原文链接:https://code.ifrontend.net/archives/1235,转载请注明出处。

评论0