113 lines
2.9 KiB
TypeScript

'use client';
import React, { useState, useEffect, useRef } from 'react';
import {
motion,
useMotionValue,
useSpring,
type SpringOptions,
} from 'motion/react';
const SPRING_CONFIG = { stiffness: 26.7, damping: 4.1, mass: 0.2 };
export type MagneticProps = {
children: React.ReactNode;
intensity?: number;
range?: number;
actionArea?: 'self' | 'parent' | 'global';
springOptions?: SpringOptions;
};
export function Magnetic({
children,
intensity = 0.6,
range = 100,
actionArea = 'self',
springOptions = SPRING_CONFIG,
}: MagneticProps) {
const [isHovered, setIsHovered] = useState(false);
const ref = useRef<HTMLDivElement>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const springX = useSpring(x, springOptions);
const springY = useSpring(y, springOptions);
useEffect(() => {
const calculateDistance = (e: MouseEvent) => {
if (ref.current) {
const rect = ref.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const distanceX = e.clientX - centerX;
const distanceY = e.clientY - centerY;
const absoluteDistance = Math.sqrt(distanceX ** 2 + distanceY ** 2);
if (isHovered && absoluteDistance <= range) {
const scale = 1 - absoluteDistance / range;
x.set(distanceX * intensity * scale);
y.set(distanceY * intensity * scale);
} else {
x.set(0);
y.set(0);
}
}
};
document.addEventListener('mousemove', calculateDistance);
return () => {
document.removeEventListener('mousemove', calculateDistance);
};
}, [ref, isHovered, intensity, range]);
useEffect(() => {
if (actionArea === 'parent' && ref.current?.parentElement) {
const parent = ref.current.parentElement;
const handleParentEnter = () => setIsHovered(true);
const handleParentLeave = () => setIsHovered(false);
parent.addEventListener('mouseenter', handleParentEnter);
parent.addEventListener('mouseleave', handleParentLeave);
return () => {
parent.removeEventListener('mouseenter', handleParentEnter);
parent.removeEventListener('mouseleave', handleParentLeave);
};
} else if (actionArea === 'global') {
setIsHovered(true);
}
}, [actionArea]);
const handleMouseEnter = () => {
if (actionArea === 'self') {
setIsHovered(true);
}
};
const handleMouseLeave = () => {
if (actionArea === 'self') {
setIsHovered(false);
x.set(0);
y.set(0);
}
};
return (
<motion.div
ref={ref}
onMouseEnter={actionArea === 'self' ? handleMouseEnter : undefined}
onMouseLeave={actionArea === 'self' ? handleMouseLeave : undefined}
style={{
x: springX,
y: springY,
}}
>
{children}
</motion.div>
);
}