mirror of
https://github.com/Techtonic-Fault/homepage.git
synced 2026-01-22 21:18:22 +00:00
111 lines
2.5 KiB
TypeScript
111 lines
2.5 KiB
TypeScript
"use client"
|
|
|
|
import type React from "react"
|
|
|
|
import { useEffect, useRef, useState } from "react"
|
|
import { motion, useAnimation, useInView } from "framer-motion"
|
|
|
|
type AnimateProps = {
|
|
children: React.ReactNode
|
|
animation?: "fadeIn" | "fadeInUp" | "fadeInDown" | "fadeInLeft" | "fadeInRight" | "zoom" | "bounce"
|
|
delay?: number
|
|
duration?: number
|
|
className?: string
|
|
once?: boolean
|
|
threshold?: number
|
|
}
|
|
|
|
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 },
|
|
},
|
|
bounce: {
|
|
hidden: { opacity: 0, y: 50 },
|
|
visible: {
|
|
opacity: 1,
|
|
y: 0,
|
|
transition: {
|
|
type: "spring",
|
|
bounce: 0.4,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
export function Animate({
|
|
children,
|
|
animation = "fadeIn",
|
|
delay = 0,
|
|
duration = 0.5,
|
|
className = "",
|
|
once = true,
|
|
threshold = 0.1,
|
|
}: AnimateProps) {
|
|
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
|
|
|
|
return (
|
|
<motion.div
|
|
ref={ref}
|
|
initial="hidden"
|
|
animate={controls}
|
|
variants={variants}
|
|
transition={{ duration, delay, ease: "easeOut" }}
|
|
className={className}
|
|
>
|
|
{children}
|
|
</motion.div>
|
|
)
|
|
}
|