Update src/components/cardStack/hooks/useDepth3DAnimation.ts
This commit is contained in:
@@ -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<HTMLDivElement | null>;
|
||||
perspectiveRef?: RefObject<HTMLDivElement | null>;
|
||||
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<number[]>,
|
||||
);
|
||||
const rotateY = useTransform(
|
||||
mouseXSpring,
|
||||
[-0.5, 0.5],
|
||||
rotationYRange as Variants<number[]>,
|
||||
);
|
||||
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<typeof transition>,
|
||||
// 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,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user