mirror of
https://github.com/Techtonic-Fault/homepage.git
synced 2026-01-23 05:26:30 +00:00
Initial commit
This commit is contained in:
109
components/animations/stagger.tsx
Normal file
109
components/animations/stagger.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
"use client"
|
||||
|
||||
import React from "react"
|
||||
|
||||
import { useEffect, useRef, useState } from "react"
|
||||
import { motion, useAnimation, useInView } from "framer-motion"
|
||||
|
||||
type StaggerProps = {
|
||||
children: React.ReactNode
|
||||
delay?: number
|
||||
staggerDelay?: number
|
||||
duration?: number
|
||||
className?: string
|
||||
once?: boolean
|
||||
threshold?: number
|
||||
animation?: "fadeIn" | "fadeInUp" | "fadeInDown" | "fadeInLeft" | "fadeInRight" | "zoom"
|
||||
}
|
||||
|
||||
const animations = {
|
||||
fadeIn: {
|
||||
hidden: { opacity: 0 },
|
||||
visible: { opacity: 1 },
|
||||
},
|
||||
fadeInUp: {
|
||||
hidden: { opacity: 0, y: 50 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
},
|
||||
fadeInDown: {
|
||||
hidden: { opacity: 0, y: -50 },
|
||||
visible: { opacity: 1, y: 0 },
|
||||
},
|
||||
fadeInLeft: {
|
||||
hidden: { opacity: 0, x: -50 },
|
||||
visible: { opacity: 1, x: 0 },
|
||||
},
|
||||
fadeInRight: {
|
||||
hidden: { opacity: 0, x: 50 },
|
||||
visible: { opacity: 1, x: 0 },
|
||||
},
|
||||
zoom: {
|
||||
hidden: { opacity: 0, scale: 0.8 },
|
||||
visible: { opacity: 1, scale: 1 },
|
||||
},
|
||||
}
|
||||
|
||||
export function Stagger({
|
||||
children,
|
||||
delay = 0,
|
||||
staggerDelay = 0.1,
|
||||
duration = 0.5,
|
||||
className = "",
|
||||
once = true,
|
||||
threshold = 0.1,
|
||||
animation = "fadeInUp",
|
||||
}: StaggerProps) {
|
||||
const controls = useAnimation()
|
||||
const ref = useRef(null)
|
||||
const isInView = useInView(ref, { once, threshold })
|
||||
const [shouldReduceMotion, setShouldReduceMotion] = useState(false)
|
||||
|
||||
// Check for prefers-reduced-motion
|
||||
useEffect(() => {
|
||||
const mediaQuery = window.matchMedia("(prefers-reduced-motion: reduce)")
|
||||
setShouldReduceMotion(mediaQuery.matches)
|
||||
|
||||
const handleChange = () => setShouldReduceMotion(mediaQuery.matches)
|
||||
mediaQuery.addEventListener("change", handleChange)
|
||||
return () => mediaQuery.removeEventListener("change", handleChange)
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (isInView) {
|
||||
controls.start("visible")
|
||||
} else if (!once) {
|
||||
controls.start("hidden")
|
||||
}
|
||||
}, [isInView, controls, once])
|
||||
|
||||
const selectedAnimation = animations[animation]
|
||||
|
||||
// If user prefers reduced motion, use a simpler animation
|
||||
const variants = shouldReduceMotion
|
||||
? {
|
||||
hidden: { opacity: 0 },
|
||||
visible: { opacity: 1 },
|
||||
}
|
||||
: selectedAnimation
|
||||
|
||||
const containerVariants = {
|
||||
hidden: { opacity: 0 },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
staggerChildren: shouldReduceMotion ? 0 : staggerDelay,
|
||||
delayChildren: delay,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div ref={ref} initial="hidden" animate={controls} variants={containerVariants} className={className}>
|
||||
{React.Children.map(children, (child) => (
|
||||
<motion.div variants={variants} transition={{ duration, ease: "easeOut" }}>
|
||||
{child}
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user