优雅的页面过渡效果组件
基于 React 和 Framer Motion 打造的现代化页面过渡效果组件,提供丰富的动画效果,让您的页面切换更加流畅自然。
源码
PageScroll.jsx
import { useRef, useState, useEffect } from "react";
import { motion, AnimatePresence } from "framer-motion";
import { transitions } from "./transitions";
// 过渡效果选择器组件
function TransitionSelector({ currentTransition, onTransitionChange }) {
return (
<div className="fixed left-8 top-8 z-50">
<select
value={currentTransition}
onChange={(e) => onTransitionChange(e.target.value)}
className="bg-white/10 text-white border border-white/20 rounded-lg px-4 py-2 backdrop-blur-sm"
>
<option value="fade">渐隐渐现</option>
<option value="radial">径向展开</option>
<option value="fold">折叠效果</option>
<option value="spiral">螺旋展开</option>
<option value="chess">棋盘格</option>
<option value="blindsH">水平百叶窗</option>
<option value="windmill">风车旋转</option>
<option value="split">双向展开</option>
<option value="push">推入效果</option>
<option value="reveal">揭开效果</option>
<option value="cylinder">圆筒翻转</option>
<option value="rubikHorizontal">水平魔方</option>
</select>
</div>
);
}
// Pagination 组件
function Pagination({ total, current, onPageChange }) {
return (
<div className="fixed right-8 top-1/2 transform -translate-y-1/2 z-50">
<div className="flex flex-col gap-3">
{Array.from({ length: total }, (_, i) => (
<motion.button
key={i}
onClick={() => onPageChange(i)}
className={`w-3 h-3 rounded-full transition-all duration-300 ${
current === i
? "bg-white scale-125"
: "bg-white/50 hover:bg-white/80"
}`}
whileHover={{ scale: 1.2 }}
whileTap={{ scale: 0.9 }}
initial={{ opacity: 0, x: 20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ delay: i * 0.1 }}
/>
))}
</div>
</div>
);
}
export default function PageScroll() {
const [currentIndex, setCurrentIndex] = useState(0);
const [currentTransition, setCurrentTransition] = useState("fade");
const containerRef = useRef(null);
const sections = [
{
color: "bg-red-500",
title: "Red Section",
desc: "A vibrant red section with dynamic animations",
},
{
color: "bg-orange-500",
title: "Orange Section",
desc: "Warm orange tones with smooth transitions",
},
{
color: "bg-yellow-500",
title: "Yellow Section",
desc: "Bright yellow energy with playful movements",
},
{
color: "bg-green-500",
title: "Green Section",
desc: "Fresh green space with natural flow",
},
{
color: "bg-blue-500",
title: "Blue Section",
desc: "Calm blue atmosphere with gentle animations",
},
{
color: "bg-indigo-500",
title: "Indigo Section",
desc: "Deep indigo depth with smooth transitions",
},
{
color: "bg-purple-500",
title: "Purple Section",
desc: "Rich purple elegance with dynamic effects",
},
];
useEffect(() => {
let isAnimating = false;
let scrollTimeout;
const animationDuration = 800; // 动画时间
const scrollThreshold = 50; // 滚动阈值
let accumulatedDelta = 0; // 累积的滚动值
const handleWheel = (e) => {
e.preventDefault();
if (isAnimating) return; // 如果正在动画中,忽略滚动事件
// 累积滚动值
accumulatedDelta += e.deltaY;
// 清除之前的定时器
clearTimeout(scrollTimeout);
// 设置新的定时器,在短暂延迟后处理滚动
scrollTimeout = setTimeout(() => {
if (Math.abs(accumulatedDelta) >= scrollThreshold) {
isAnimating = true;
// 根据累积的滚动值决定方向
if (accumulatedDelta > 0 && currentIndex < sections.length - 1) {
setCurrentIndex((prev) => prev + 1);
} else if (accumulatedDelta < 0 && currentIndex > 0) {
setCurrentIndex((prev) => prev - 1);
}
// 重置累积值
accumulatedDelta = 0;
// 动画结束后解除锁定
setTimeout(() => {
isAnimating = false;
}, animationDuration);
}
}, 50); // 50ms的防抖延迟
};
const container = containerRef.current;
if (container) {
container.addEventListener("wheel", handleWheel, { passive: false });
return () => {
container.removeEventListener("wheel", handleWheel);
clearTimeout(scrollTimeout);
};
}
}, [currentIndex, sections.length]);
return (
<div ref={containerRef} className="h-screen overflow-hidden">
<TransitionSelector
currentTransition={currentTransition}
onTransitionChange={setCurrentTransition}
/>
<Pagination
total={sections.length}
current={currentIndex}
onPageChange={setCurrentIndex}
/>
<AnimatePresence mode="popLayout">
<Section
key={currentIndex}
section={sections[currentIndex]}
transition={transitions[currentTransition]}
/>
</AnimatePresence>
</div>
);
}
function Section({
section = {
color: "bg-gray-900",
title: "",
desc: "",
},
transition,
}) {
// 打字机效果的变体
const titleVariants = {
initial: { opacity: 0 },
animate: {
opacity: 1,
transition: {
staggerChildren: 0.05,
},
},
exit: {
opacity: 0,
transition: {
duration: 0.5,
},
},
};
const letterVariants = {
initial: { opacity: 0, y: 20 },
animate: {
opacity: 1,
y: 0,
},
};
// 描述文本的变体
const descVariants = {
initial: { opacity: 0, y: 50 },
animate: {
opacity: 1,
y: 0,
transition: {
duration: 0.8,
ease: "easeOut",
delay: 0.5,
},
},
exit: {
opacity: 0,
y: -50,
transition: {
duration: 0.5,
},
},
};
return (
<motion.div
className={`${section.color} h-screen w-full flex items-center justify-center absolute inset-0`}
{...transition}
>
<div className="text-center px-4">
<motion.div
variants={titleVariants}
initial="initial"
animate="animate"
exit="exit"
>
<motion.h2 className="text-6xl font-bold text-white mb-6 overflow-hidden">
{section.title.split("").map((char, index) => (
<motion.span
key={index}
variants={letterVariants}
style={{ display: "inline-block" }}
>
{char}
</motion.span>
))}
</motion.h2>
</motion.div>
<motion.p
className="text-xl text-white/80 max-w-2xl mx-auto"
variants={descVariants}
initial="initial"
animate="animate"
exit="exit"
>
{section.desc}
</motion.p>
</div>
</motion.div>
);
}
transitions.js 特效配置
export const transitions = {
// 渐隐渐现效果
fade: {
initial: { opacity: 0 },
animate: { opacity: 1 },
exit: { opacity: 0, position: "absolute" },
transition: { duration: 0.3, ease: "easeInOut" },
},
// 径向展开效果
radial: {
initial: {
opacity: 0,
clipPath: "circle(0% at 50% 50%)",
},
animate: {
opacity: 1,
clipPath: "circle(100% at 50% 50%)",
},
exit: {
opacity: 0,
clipPath: "circle(0% at 50% 50%)",
},
transition: { duration: 0.8, ease: "easeInOut" },
},
// 推入效果
push: {
initial: { x: "100%", position: "absolute" },
animate: { x: 0, position: "absolute" },
exit: { x: "-100%", position: "absolute" },
transition: {
duration: 0.4,
ease: [0.4, 0, 0.2, 1],
},
},
// 揭开效果
reveal: {
initial: {
opacity: 0,
clipPath: "inset(0 50% 0 50%)",
position: "absolute",
},
animate: {
opacity: 1,
clipPath: "inset(0 0% 0 0%)",
position: "absolute",
},
exit: {
opacity: 0,
clipPath: "inset(0 0% 0 100%)",
position: "absolute",
},
transition: {
duration: 0.45,
ease: [0.4, 0, 0.2, 1],
},
},
// 折叠效果
fold: {
initial: {
opacity: 0,
clipPath: "polygon(50% 0%, 50% 0%, 50% 100%, 50% 100%)",
position: "absolute",
},
animate: {
opacity: 1,
clipPath: "polygon(0% 0%, 100% 0%, 100% 100%, 0% 100%)",
position: "absolute",
},
exit: {
opacity: 0,
clipPath: "polygon(50% 0%, 50% 0%, 50% 100%, 50% 100%)",
position: "absolute",
},
transition: {
duration: 0.4,
ease: [0.4, 0, 0.2, 1],
},
},
// 螺旋展开
spiral: {
initial: {
opacity: 0,
scale: 0,
rotate: -180,
position: "absolute",
},
animate: {
opacity: 1,
scale: 1,
rotate: 0,
position: "absolute",
},
exit: {
opacity: 0,
scale: 0,
rotate: 180,
position: "absolute",
},
transition: {
duration: 0.5,
ease: [0.4, 0, 0.2, 1],
},
},
// 棋盘格
chess: {
initial: {
opacity: 0,
clipPath: "inset(0 0 100% 100%)",
position: "absolute",
},
animate: {
opacity: 1,
clipPath: "inset(0 0 0 0)",
position: "absolute",
},
exit: {
opacity: 0,
clipPath: "inset(100% 100% 0 0)",
position: "absolute",
},
transition: {
duration: 0.4,
ease: [0.4, 0, 0.2, 1],
},
},
// 百叶窗水平
blindsH: {
initial: {
opacity: 0,
clipPath: "inset(0 100% 0 0)",
position: "absolute",
},
animate: {
opacity: 1,
clipPath: "inset(0 0 0 0)",
position: "absolute",
},
exit: {
opacity: 0,
clipPath: "inset(0 0 0 100%)",
position: "absolute",
},
transition: {
duration: 0.4,
ease: [0.4, 0, 0.2, 1],
},
},
// 风车旋转
windmill: {
initial: {
opacity: 0,
clipPath: "polygon(50% 50%, 50% 0, 50% 0, 50% 50%)",
rotate: -180,
position: "absolute",
},
animate: {
opacity: 1,
clipPath: "polygon(0 0, 100% 0, 100% 100%, 0 100%)",
rotate: 0,
position: "absolute",
},
exit: {
opacity: 0,
clipPath: "polygon(50% 50%, 100% 50%, 100% 50%, 50% 50%)",
rotate: 180,
position: "absolute",
},
transition: {
duration: 0.6,
ease: [0.4, 0, 0.2, 1],
},
},
// 双向展开
split: {
initial: {
opacity: 0,
clipPath: "inset(50% 50% 50% 50%)",
position: "absolute",
},
animate: {
opacity: 1,
clipPath: "inset(0 0 0 0)",
position: "absolute",
},
exit: {
opacity: 0,
clipPath: "inset(0 50% 0 50%)",
position: "absolute",
},
transition: {
duration: 0.45,
ease: [0.4, 0, 0.2, 1],
},
},
// 圆筒翻转效果
cylinder: {
initial: {
opacity: 0,
transform: [
"perspective(2000px)",
"rotateX(-90deg)",
"translateY(100%)",
].join(" "),
transformStyle: "preserve-3d",
transformOrigin: "bottom center",
position: "absolute",
},
animate: {
opacity: 1,
transform: ["perspective(2000px)", "rotateX(0deg)", "translateY(0)"].join(
" "
),
transformStyle: "preserve-3d",
transformOrigin: "center",
position: "absolute",
},
exit: {
opacity: 0,
transform: [
"perspective(2000px)",
"rotateX(90deg)",
"translateY(-100%)",
].join(" "),
transformStyle: "preserve-3d",
transformOrigin: "top center",
position: "absolute",
},
transition: {
duration: 0.8,
ease: [0.4, 0, 0.2, 1],
},
},
// 水平魔方翻转
rubikHorizontal: {
initial: {
opacity: 0,
transform: [
"perspective(2000px)",
"rotateY(-90deg)",
"translateX(-100%)",
"translateZ(-200px)",
].join(" "),
transformStyle: "preserve-3d",
transformOrigin: "left center",
position: "absolute",
},
animate: {
opacity: 1,
transform: [
"perspective(2000px)",
"rotateY(0deg)",
"translateX(0)",
"translateZ(0)",
].join(" "),
transformStyle: "preserve-3d",
transformOrigin: "center",
position: "absolute",
},
exit: {
opacity: 0,
transform: [
"perspective(2000px)",
"rotateY(90deg)",
"translateX(100%)",
"translateZ(-200px)",
].join(" "),
transformStyle: "preserve-3d",
transformOrigin: "right center",
position: "absolute",
},
transition: {
duration: 1,
ease: [0.4, 0, 0.2, 1],
},
},
};
✨ 特性
- 🎨 丰富的过渡效果
- 🚀 流畅的动画表现
- 📱 响应式设计
- 🛠️ 易于集成
- ⚙️ 高度可定制
- 🎯 直观的 API
🎬 内置过渡效果
基础效果
- 渐隐渐现:简约而优雅的透明度过渡
- 径向展开:从中心点扩散的圆形展开效果
- 折叠效果:类似折纸的立体折叠动画
- 螺旋展开:旋转缩放的螺旋动画
- 棋盘格:方块矩阵的错落过渡
- 水平百叶窗:横向百叶窗切换效果
- 风车旋转:中心旋转的风车动画
- 双向展开:水平或垂直方向的展开效果
3D 效果
- 推入效果:3D 空间的推入动画
- 揭开效果:立体的揭开翻转效果
- 圆筒翻转:圆柱形的立体翻转
- 水平魔方:经典的魔方旋转效果
🎨 自定义过渡效果
每个过渡效果都可以通过以下属性进行自定义:
{
initial: {}, // 初始状态
animate: {}, // 动画状态
exit: {}, // 退出状态
transition: {
duration: 0.8,
ease: [0.4, 0, 0.2, 1]
}
}
🛠️ 技术栈
- React
- Framer Motion
- Tailwind CSS
- TypeScript
原文链接:https://code.ifrontend.net/archives/415,转载请注明出处。
评论0