ComponentsButton Magnetic

Button Magnetic

Terminal
npm i framer-motion clsx tailwind-merge
utils/cn.ts
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}
ButtonMagnetic.tsx
"use client"; // @NOTE: add in case you are using Next.js

import { useRef, useState } from "react";

import { motion } from "framer-motion";

import { cn } from "@/utils/cn";

export function ButtonMagneticExample() {
  return <ButtonMagnetic>Move Me</ButtonMagnetic>;
}

type ButtonMagneticProps = React.ComponentProps<"button">;

function ButtonMagnetic({ className, children }: ButtonMagneticProps) {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const ref = useRef<any>(null);

  function handleMouseMove(e: React.MouseEvent) {
    const { clientX, clientY } = e;
    const { height, width, left, top } = ref.current.getBoundingClientRect();

    const middleX = clientX - (left + width / 2);
    const middleY = clientY - (top + height / 2);

    setPosition({ x: middleX, y: middleY });
  }

  function handleMouseLeave() {
    setPosition({ x: 0, y: 0 });
  }

  const { x, y } = position;

  return (
    <motion.button
      ref={ref}
      className={cn(
        "relative rounded-xl bg-white px-4 py-2 text-sm font-semibold text-black",
        className,
      )}
      onMouseMove={handleMouseMove}
      onMouseLeave={handleMouseLeave}
      animate={{ x, y }}
      transition={{
        type: "spring",
        damping: 15,
        stiffness: 150,
        mass: 0.1,
      }}
    >
      {children}
    </motion.button>
  );
}