Magnetic Button

Button that warps toward the cursor with elastic spring physics

animationbuttonmagneticinteractive
Install dependencies
$npm install framer-motion
Preview

Source Code

"use client";

import { useRef, useState } from "react";
import { motion, useSpring } from "framer-motion";

interface MagneticButtonProps {
  children: React.ReactNode;
  strength?: number;
  radius?: number;
  className?: string;
}

export default function MagneticButton({
  children,
  strength = 40,
  radius = 200,
  className = "",
}: MagneticButtonProps) {
  const ref = useRef<HTMLButtonElement>(null);
  const [isHovered, setIsHovered] = useState(false);

  const springConfig = { damping: 15, stiffness: 150, mass: 0.1 };
  const x = useSpring(0, springConfig);
  const y = useSpring(0, springConfig);

  const handleMouseMove = (e: React.MouseEvent) => {
    if (!ref.current) return;
    const rect = ref.current.getBoundingClientRect();
    const centerX = rect.left + rect.width / 2;
    const centerY = rect.top + rect.height / 2;

    const distX = e.clientX - centerX;
    const distY = e.clientY - centerY;
    const distance = Math.sqrt(distX * distX + distY * distY);

    if (distance < radius) {
      const pull = (1 - distance / radius) * strength;
      x.set((distX / distance) * pull);
      y.set((distY / distance) * pull);
      if (!isHovered) setIsHovered(true);
    } else {
      x.set(0);
      y.set(0);
      if (isHovered) setIsHovered(false);
    }
  };

  const handleMouseLeave = () => {
    x.set(0);
    y.set(0);
    setIsHovered(false);
  };

  return (
    <div
      className="inline-flex items-center justify-center p-16"
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
    >
      <motion.button
        ref={ref}
        style={{ x, y }}
        whileTap={{ scale: 0.95 }}
        className={`relative cursor-pointer rounded-full bg-gradient-to-r from-indigo-500 to-purple-600 px-8 py-4 text-lg font-semibold text-white transition-shadow hover:shadow-[0_8px_30px_rgba(99,102,241,0.35)] ${className}`}
      >
        <motion.span
          style={{
            x: x.get() * 0.3,
            y: y.get() * 0.3,
          }}
          className="relative z-10 block"
        >
          {children}
        </motion.span>
      </motion.button>
    </div>
  );
}