简介
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