Random Access Components

Magnet

Wrapper component to add a magnetic effect to your elements

Motion iconMotion
TailwindCSS iconTailwind CSS
React IconReact
Next.js IconNext.js

Demo

Hover me
High effect
Magnet: Inactive
"use client";
import Magnet from "./magnet";
import { StarIcon } from "lucide-react";
import { useState } from "react";

const MagnetDemo: React.FC = () => {

    const [isActive, setIsActive] = useState(false);

    return ( 
      <div className="flex flex-col md:flex-row w-full items-center justify-around gap-20 md:gap-0">
          <Magnet>
              <div>Hover me</div>
          </Magnet>
          <Magnet className="rounded-full bg-accent cursor-pointer text-accent-foreground p-3 border border-accent-foreground/10">
              <StarIcon className="size-6" />
          </Magnet>
          <Magnet magnetMultiplier={3}>
              High effect 
          </Magnet>
          <div className="flex flex-col gap-4 justify-center items-center">
              <Magnet isActive={isActive}>
                  Magnet: {isActive ? "Active" : "Inactive"}
              </Magnet>
              <button onClick={() => setIsActive(!isActive)} className="text-xs">
                  Toggle
              </button>
          </div>
      </div>
    );
}
 
export { MagnetDemo };

Installation

Install the following dependencies:

npm install motion
pnpm add motion
yarn add motion
bun add motion

Copy and paste the following code into your project.

magnet.tsx
"use client";

import { useEffect, useRef, useState } from "react";
import { motion, useSpring } from "motion/react";
import { cn } from "@/lib/utils";

interface MagnetProps {
  children: React.ReactNode;
  className?: string;
  /**
   * To programmatically activate/deactivate the magnetic effect
   */
  isActive?: boolean;

  /**
   * Default is 1. It multiplies the force of the magnetic effect.
   */
  magnetMultiplier?: number;
}

const Magnet: React.FC<MagnetProps> = ({
  children,
  className,
  isActive = true,
  magnetMultiplier = 1,
}) => {
  const ref = useRef<HTMLDivElement>(null);

  const [position, setPosition] = useState({ x: 0, y: 0 });

  const mouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    if (!isActive || !ref.current) return;
    const { clientX, clientY } = e;

    const { width, height, left, top } = ref.current.getBoundingClientRect();
    const x =
      (clientX - (left + width / 2)) *
      (magnetMultiplier < 0 ? 1 : (magnetMultiplier < 0 ? 1 : magnetMultiplier));
    const y =
      (clientY - (top + height / 2)) *
      (magnetMultiplier < 0 ? 1 : (magnetMultiplier < 0 ? 1 : magnetMultiplier));
    setPosition({ x, y });
  };

  const mouseLeave = () => {
    setPosition({ x: 0, y: 0 });
  };

  const x = useSpring(position.x, { stiffness: 400, damping: 60, mass: 0.2 });
  const y = useSpring(position.y, { stiffness: 400, damping: 60, mass: 0.2 });

  useEffect(() => {
    x.set(position.x);
    y.set(position.y);
  }, [position]);

  useEffect(() => {
    if (!isActive) {
      setPosition({ x: 0, y: 0 });
    }
  }, [isActive]);

  return (
    <motion.div
      className={cn('relative', className)}
      ref={ref}
      whileHover={isActive ? { scale: 1.1 } : {}}
      transition={{ type: 'spring', stiffness: 300 }}
      onMouseMove={mouseMove}
      onMouseLeave={mouseLeave}
      style={{ x, y }}
    >
      {children}
    </motion.div>
  );
};

export default Magnet;

Usage

import Magnet from "@/components/magnet";
// Default usage
<Magnet>
    <div>Hover me</div>
</Magnet>
// Custom magnet effect
<Magnet magnetMultiplier={1.2}>
    <div>Hover me</div>
</Magnet>

Props

PropTypeDefault ValueDescription
childrenReactNode-The content of the magnet
className?string-Additional classes for the magnet
magnetMultiplier?number1The multiplier for the magnetic effect
isActive?booleantrueWhether the magnetic effect is active

On this page