所有分类
  • 所有分类
  • Html5资源
  • React资源
  • Vue资源
  • Php资源
  • ‌小程序资源
  • Python资源

从零开始:Vue3-Motion 打造流畅的终极动画体验

概述

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

评论0

显示验证码
没有账号?注册  忘记密码?