agency-web/app/components/Magnetic.tsx

53 lines
1.4 KiB
TypeScript

"use client";
/**
* Magnetic wrapper: the child is pulled toward the cursor while hovered,
* then springs back on leave. Transform-only (GPU). No-op on coarse pointers
* and with prefers-reduced-motion, so keyboard/touch users get a static,
* fully-clickable element.
*/
import { useRef } from "react";
import { motion, useMotionValue, useSpring } from "motion/react";
export default function Magnetic({
children,
strength = 0.4,
className,
}: {
children: React.ReactNode;
strength?: number;
className?: string;
}) {
const ref = useRef<HTMLSpanElement>(null);
const x = useMotionValue(0);
const y = useMotionValue(0);
const sx = useSpring(x, { stiffness: 250, damping: 18, mass: 0.4 });
const sy = useSpring(y, { stiffness: 250, damping: 18, mass: 0.4 });
const onMove = (e: React.PointerEvent) => {
if (e.pointerType !== "mouse") return;
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
const el = ref.current;
if (!el) return;
const r = el.getBoundingClientRect();
x.set((e.clientX - (r.left + r.width / 2)) * strength);
y.set((e.clientY - (r.top + r.height / 2)) * strength);
};
const reset = () => {
x.set(0);
y.set(0);
};
return (
<motion.span
ref={ref}
className={className}
onPointerMove={onMove}
onPointerLeave={reset}
style={{ x: sx, y: sy, display: "inline-block" }}
>
{children}
</motion.span>
);
}