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

Motion – 让动画开发变得简单而强大,使JavaScript 动画的拥有无限潜力

简介

Motion 是一个强大的动画库,它提供了简单而灵活的方式来创建流畅的动画效果。本文将详细介绍 Motion 的核心概念、常用功能以及实际应用场景,帮助你掌握这个强大的动画工具。

示例

Gestures 手势动画

whileHover 属性会在鼠标悬停时触发动画,rotate 属性会使元素旋转 360 度。transition 属性用于设置动画的持续时间和缓动函数。

import { motion } from "motion/react";

export default function Default() {
  return (
    <>
      {/* 移入旋转360度 */}
      <motion.button
        whileHover={{ rotate: 360 }}
        transition={{ duration: 0.5 }}
        className="w-20 h-20"
      >
        <div className="w-20 h-20 bg-red-500"></div>
      </motion.button>
    </>
  );
}

如果需要点击实现,可以使用 whileTap 属性。但是实际情况并不能旋转 360 度。因为,在点击时,元素会立即回到原始状态,无法完成完整的旋转。
如果需要实现,可以使用 animate 属性。

import { motion } from "motion/react";
import { useState } from "react";

export default function Default() {
  const [rotation, setRotation] = useState(0);

  const handleClick = () => {
    setRotation(rotation + 360);
  };

  return (
    <>
      {/* 点击旋转360度 */}
      <motion.button
        animate={{ rotate: rotation }}
        transition={{ duration: 0.5 }}
        onClick={handleClick}
        className="w-20 h-20"
      >
        <div className="w-20 h-20 bg-red-500"></div>
      </motion.button>
    </>
  );
}

Layout 布局动画

import { motion } from "motion/react";
import { useState } from "react";

export default function Default() {
  const [isExpanded, setIsExpanded] = useState(false);

  return (
    <>
      <button
        onClick={() => setIsExpanded(!isExpanded)}
        className="mb-4 px-4 py-2 bg-gray-200 rounded hover:bg-gray-300"
      >
        {isExpanded ? "收起" : "展开"}
      </button>

      <motion.div
        layout
        className={`${
          isExpanded ? "w-full" : "w-32"
        } h-32 bg-blue-500 rounded-lg`}
        transition={{ duration: 0.5, type: "spring" }}
      />
    </>
  );
}

Scroll 滚动动画

import { motion } from "motion/react";

export default function Default() {
  return (
    <div className="relative h-80 mb-12 overflow-hidden rounded-lg bg-gradient-to-r from-purple-500 to-blue-500">
      <motion.div
        className="absolute inset-0 flex items-center justify-center"
        initial={{ y: 100 }}
        whileInView={{ y: 0 }}
        viewport={{ once: false }}
        transition={{ duration: 0.8, ease: "easeOut" }}
      >
        <h3 className="text-3xl font-bold text-white">视差滚动效果</h3>
      </motion.div>
    </div>
  );
}

Transition 过渡动画

transition 属性用于设置动画的持续时间和缓动函数。

<motion.div
  initial={{ opacity: 0, y: 100 }}
  animate={{ opacity: 1, y: 0 }}
  exit={{ opacity: 0, y: -100 }}
  transition={{ duration: 0.5 }}
>
  <h1>Transitions</h1>
</motion.div>

路由切换页面动画

PageTransition.jsx

import { motion } from "motion/react";
import { useLocation } from "react-router-dom";

const PageTransition = ({ children }) => {
  const location = useLocation();

  return (
    <motion.div
      key={location.pathname}
      initial={{ x: "100%" }} // 从右侧开始
      animate={{ x: 0 }} // 滑动到中间
      exit={{ x: "-100%" }} // 从左侧退出
      transition={{ duration: 0.3 }}
      style={{ width: "100%", height: "100%" }}
    >
      {children}
    </motion.div>
  );
};

export default PageTransition;

App.jsx

import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AnimatePresence } from "motion/react";

// 引入页面组件
import Home from "@/pages/Home";
import About from "@/pages/About";
import PageTransition from "@/components/motion/PageTransition";

function App() {
  return (
    <BrowserRouter>
      <AnimatePresence mode="wait">
        <Routes>
          <Route
            path="/"
            element={
              <PageTransition>
                <Home />
              </PageTransition>
            }
          />
          <Route
            path="/about"
            element={
              <PageTransition>
                <About />
              </PageTransition>
            }
          />
        </Routes>
      </AnimatePresence>
    </BrowserRouter>
  );
}

export default App;

打字机效果

import { motion, useAnimationControls } from "motion/react";
import { useEffect, useState } from "react";

const Typewriter = ({ text, speed = 50, className = "" }) => {
  const [displayedText, setDisplayedText] = useState("");
  const [currentIndex, setCurrentIndex] = useState(0);
  const controls = useAnimationControls();

  useEffect(() => {
    if (currentIndex < text.length) {
      const timeout = setTimeout(() => {
        setDisplayedText((prev) => prev + text[currentIndex]);
        setCurrentIndex((prev) => prev + 1);
        controls.start({ opacity: 1, transition: { duration: 0.1 } });
      }, speed);

      return () => clearTimeout(timeout);
    }
  }, [currentIndex, text, speed, controls]);

  return (
    <div className={className}>
      <span>{displayedText}</span>
      <motion.span
        initial={{ opacity: 0 }}
        animate={controls}
        transition={{ repeat: Infinity, repeatType: "reverse", duration: 0.5 }}
      >
        |
      </motion.span>
    </div>
  );
};

export default Typewriter;

页面视差滚动效果

import { motion, useAnimation, useInView } from "motion/react";
import { useState, useEffect, useRef } from "react";

// 打字机效果组件
const TypewriterText = ({ text, delay = 50 }) => {
  const [displayText, setDisplayText] = useState("");
  const [currentIndex, setCurrentIndex] = useState(0);
  const containerRef = useRef(null);
  const isInView = useInView(containerRef, { once: true, amount: 0.5 });

  useEffect(() => {
    if (!isInView) return;

    if (currentIndex < text.length) {
      const timeout = setTimeout(() => {
        setDisplayText((prev) => prev + text[currentIndex]);
        setCurrentIndex(currentIndex + 1);
      }, delay);

      return () => clearTimeout(timeout);
    }
  }, [currentIndex, delay, text, isInView]);

  return <span ref={containerRef}>{displayText}</span>;
};

// 单个屏幕部分组件
const Section = ({ title, description, bgColor }) => {
  const ref = useRef(null);
  const isInView = useInView(ref, { once: true, amount: 0.3 });
  const controls = useAnimation();

  useEffect(() => {
    if (isInView) {
      controls.start("visible");
    }
  }, [isInView, controls]);

  const titleVariants = {
    hidden: { opacity: 0, y: 50 },
    visible: {
      opacity: 1,
      y: 0,
      transition: {
        duration: 0.8,
        delay: 0.2,
        ease: "easeOut",
      },
    },
  };

  const descVariants = {
    hidden: { opacity: 0, y: 30 },
    visible: {
      opacity: 1,
      y: 0,
      transition: {
        duration: 0.6,
        delay: 0.5,
        ease: "easeOut",
      },
    },
  };

  return (
    <div
      ref={ref}
      className={`min-h-screen flex flex-col items-center justify-center p-8 ${bgColor}`}
      style={{
        scrollSnapAlign: "start",
      }}
    >
      <motion.h2
        className="text-4xl md:text-6xl font-bold mb-6 text-center"
        initial="hidden"
        animate={controls}
        variants={titleVariants}
      >
        <TypewriterText text={title} delay={70} />
      </motion.h2>

      <motion.p
        className="text-xl md:text-2xl max-w-2xl text-center"
        initial="hidden"
        animate={controls}
        variants={descVariants}
      >
        {description}
      </motion.p>

      <motion.div
        className="absolute bottom-8"
        initial={{ opacity: 0 }}
        animate={{ opacity: 1 }}
        transition={{ delay: 1.5, duration: 1 }}
      >
        <p className="text-sm opacity-70">滚动查看更多 ↓</p>
      </motion.div>
    </div>
  );
};

export default function Default() {
  const sections = [
    {
      title: "欢迎来到视差滚动世界",
      description:
        "这是一个使用Framer Motion实现的视差滚动效果演示,向下滚动探索更多内容。",
      bgColor: "bg-blue-100",
    },
    {
      title: "无缝过渡体验",
      description:
        "每个部分都有独特的动画效果,标题使用打字机效果,描述使用淡入效果。",
      bgColor: "bg-green-100",
    },
    {
      title: "响应式设计",
      description:
        "页面适配各种屏幕尺寸,无论是在手机还是桌面设备上都能获得良好的用户体验。",
      bgColor: "bg-purple-100",
    },
    {
      title: "自定义动画效果",
      description:
        "使用Framer Motion可以轻松创建各种复杂的动画效果,提升用户交互体验。",
      bgColor: "bg-yellow-100",
    },
    {
      title: "开始创建你的动画",
      description:
        "现在你已经了解了基本效果,可以开始创建你自己的视差滚动页面了!",
      bgColor: "bg-red-100",
    },
  ];

  return (
    <div className="snap-y snap-mandatory h-screen overflow-y-scroll">
      {sections.map((section, index) => (
        <Section
          key={index}
          title={section.title}
          description={section.description}
          bgColor={section.bgColor}
        />
      ))}
    </div>
  );
}
原文链接:https://code.ifrontend.net/archives/404,转载请注明出处。
0

评论0

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