From d19131c2f711512b9ccc4874908059b7e65da612 Mon Sep 17 00:00:00 2001 From: bender Date: Wed, 3 Jun 2026 21:49:41 +0000 Subject: [PATCH] Update src/components/cardStack/hooks/useDepth3DAnimation.ts --- .../cardStack/hooks/useDepth3DAnimation.ts | 184 ++++++++---------- 1 file changed, 77 insertions(+), 107 deletions(-) diff --git a/src/components/cardStack/hooks/useDepth3DAnimation.ts b/src/components/cardStack/hooks/useDepth3DAnimation.ts index 1966225..2593e44 100644 --- a/src/components/cardStack/hooks/useDepth3DAnimation.ts +++ b/src/components/cardStack/hooks/useDepth3DAnimation.ts @@ -1,118 +1,88 @@ -import { useEffect, useState, useRef, RefObject } from "react"; +import { useEffect } from 'react'; +import { useMotionValue, useSpring, useTransform } from 'framer-motion'; +import { Variants } from '@/types/AnimatePresence'; -const MOBILE_BREAKPOINT = 768; -const ANIMATION_SPEED = 0.05; -const ROTATION_SPEED = 0.1; -const MOUSE_MULTIPLIER = 0.5; -const ROTATION_MULTIPLIER = 0.25; - -interface UseDepth3DAnimationProps { - itemRefs: RefObject<(HTMLElement | null)[]>; - containerRef: RefObject; - perspectiveRef?: RefObject; - isEnabled: boolean; +interface Depth3DAnimationProps { + perspective?: number; + depthFactor?: number; + hoverScale?: number; + transition?: { + duration?: number; + ease?: string; + }; + springOptions?: { + stiffness?: number; + damping?: number; + mass?: number; + }; + rotationXRange?: number[]; + rotationYRange?: number[]; } export const useDepth3DAnimation = ({ - itemRefs, - containerRef, - perspectiveRef, - isEnabled, -}: UseDepth3DAnimationProps) => { - const [isMobile, setIsMobile] = useState(false); + perspective = 2000, + depthFactor = 20, + hoverScale = 1.05, + transition = { duration: 0.8, ease: [0.6, 0.01, -0.05, 0.9] }, + springOptions = { stiffness: 400, damping: 10, mass: 1 }, + rotationXRange = [-10, 10], + rotationYRange = [-10, 10], +}: Depth3DAnimationProps) => { + const x = useMotionValue(0); + const y = useMotionValue(0); + + const mouseXSpring = useSpring(x, springOptions); + const mouseYSpring = useSpring(y, springOptions); + + const rotateX = useTransform( + mouseYSpring, + [-0.5, 0.5], + rotationXRange as Variants, + ); + const rotateY = useTransform( + mouseXSpring, + [-0.5, 0.5], + rotationYRange as Variants, + ); + const scale = useTransform(mouseXSpring, [-0.5, 0.5], [1, hoverScale]); + + const handleMouseMove = (event: React.MouseEvent) => { + const rect = (event.target as HTMLElement).getBoundingClientRect(); + const width = rect.width; + const height = rect.height; + const mouseX = event.clientX - rect.left; + const mouseY = event.clientY - rect.top; + const xPct = mouseX / width - 0.5; + const yPct = mouseY / height - 0.5; + x.set(xPct); + y.set(yPct); + }; + + const handleMouseLeave = () => { + x.set(0); + y.set(0); + }; - // Detect mobile viewport useEffect(() => { - const checkMobile = () => { - setIsMobile(window.innerWidth < MOBILE_BREAKPOINT); - }; - - checkMobile(); - window.addEventListener("resize", checkMobile); - + // Cleanup function if needed, though Framer Motion handles many subscriptions internally. return () => { - window.removeEventListener("resize", checkMobile); + // No explicit cleanup for motion values in this simple case, but good to keep in mind. }; }, []); - // 3D mouse-tracking effect (desktop only) - useEffect(() => { - if (!isEnabled || isMobile) return; - - let animationFrameId: number; - let isAnimating = true; - - // Apply perspective to the perspective ref (grid) if provided, otherwise to container (section) - const perspectiveElement = perspectiveRef?.current || containerRef.current; - if (perspectiveElement) { - perspectiveElement.style.perspective = "1200px"; - perspectiveElement.style.transformStyle = "preserve-3d"; - } - - let mouseX = 0; - let mouseY = 0; - let isMouseInSection = false; - - let currentX = 0; - let currentY = 0; - let currentRotationX = 0; - let currentRotationY = 0; - - const handleMouseMove = (event: MouseEvent): void => { - if (containerRef.current) { - const rect = containerRef.current.getBoundingClientRect(); - isMouseInSection = - event.clientX >= rect.left && - event.clientX <= rect.right && - event.clientY >= rect.top && - event.clientY <= rect.bottom; - } - - if (isMouseInSection) { - mouseX = (event.clientX / window.innerWidth) * 100 - 50; - mouseY = (event.clientY / window.innerHeight) * 100 - 50; - } - }; - - const animate = (): void => { - if (!isAnimating) return; - - if (isMouseInSection) { - const distX = mouseX * MOUSE_MULTIPLIER - currentX; - const distY = mouseY * MOUSE_MULTIPLIER - currentY; - currentX += distX * ANIMATION_SPEED; - currentY += distY * ANIMATION_SPEED; - - const distRotX = -mouseY * ROTATION_MULTIPLIER - currentRotationX; - const distRotY = mouseX * ROTATION_MULTIPLIER - currentRotationY; - currentRotationX += distRotX * ROTATION_SPEED; - currentRotationY += distRotY * ROTATION_SPEED; - } else { - currentX += -currentX * ANIMATION_SPEED; - currentY += -currentY * ANIMATION_SPEED; - currentRotationX += -currentRotationX * ROTATION_SPEED; - currentRotationY += -currentRotationY * ROTATION_SPEED; - } - - itemRefs.current?.forEach((ref) => { - if (!ref) return; - ref.style.transform = `translate(${currentX}px, ${currentY}px) rotateX(${currentRotationX}deg) rotateY(${currentRotationY}deg)`; - }); - - animationFrameId = requestAnimationFrame(animate); - }; - - animate(); - window.addEventListener("mousemove", handleMouseMove); - - return () => { - window.removeEventListener("mousemove", handleMouseMove); - if (animationFrameId) { - cancelAnimationFrame(animationFrameId); - } - isAnimating = false; - }; - }, [isEnabled, isMobile, itemRefs, containerRef]); - - return { isMobile }; + return { + style: { + perspective: perspective + 'px', + transformStyle: 'preserve-3d', + rotateX, + rotateY, + scale, + transition: transition as Variants, + // The depth effect is implicitly handled by the perspective and rotation transform + // Additional translateZ can be added for more explicit depth if needed + translateZ: depthFactor + 'px', + }, + onMouseMove: handleMouseMove, + onMouseLeave: handleMouseLeave, + }; };