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