Follow Cursor Card

A card that follows the cursor around the screen.

Gif for follow cursor card example

Installation

Copy and paste the source code into your project.

"use client";
 
import React, { useCallback, useEffect, useRef, useState } from "react";
import {
  motion,
  useMotionTemplate,
  useMotionValue,
  useSpring,
} from "framer-motion";
import { cn } from "@/lib/utils";
 
const MAX_ROTATION = 25;
const STIFF_DAMPING = 30;
const STIFFNESS = 500;
 
type Props = {
  children: React.ReactNode;
  outerBackground?: string;
};
 
/**
 * FollowCursorCard component.
 *
 * This component creates a card that follows the cursor's movement on the screen.
 *
 * @param {React.ReactNode} props.children - The content of the card.
 * @param {string} [props.outerBackground="bg-gradient-to-br from-yellow-500 to-orange-400"] - The background color of the outer card in tailwind classes.
 *
 */
export const FollowCursorCard = ({
  children,
  outerBackground = "bg-gradient-to-br from-yellow-500 to-orange-400",
}: Props) => {
  const ref = useRef<HTMLDivElement | null>(null);
  const childRef = useRef<HTMLDivElement | null>(null);
 
  const [outerDimensions, setOuterDimensions] = useState({
    width: 0,
    height: 0,
  });
 
  const x = useMotionValue(0);
  const y = useMotionValue(0);
 
  const xSpring = useSpring(x, {
    stiffness: STIFFNESS,
    damping: STIFF_DAMPING,
  });
  const ySpring = useSpring(y, {
    stiffness: STIFFNESS,
    damping: STIFF_DAMPING,
  });
 
  const transform = useMotionTemplate`rotateX(${xSpring}deg) rotateY(${ySpring}deg)`;
 
  const handleMouseMove = useCallback(
    (e: MouseEvent) => {
      if (!ref.current) return;
 
      const width = window.innerWidth;
      const height = window.innerHeight;
 
      const mouseX = (e.clientX - width / 2) / width;
      const mouseY = (e.clientY - height / 2) / height;
 
      let rX = mouseY * -MAX_ROTATION;
      let rY = mouseX * MAX_ROTATION;
 
      rX = Math.max(Math.min(rX, MAX_ROTATION), -MAX_ROTATION);
      rY = Math.max(Math.min(rY, MAX_ROTATION), -MAX_ROTATION);
 
      x.set(rX);
      y.set(rY);
    },
    [ref, x, y]
  );
 
  useEffect(() => {
    window.addEventListener("mousemove", handleMouseMove);
 
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, [handleMouseMove]);
 
  useEffect(() => {
    if (childRef.current) {
      const resizeObserver = new ResizeObserver((entries) => {
        for (let entry of entries) {
          const { width, height } = entry.contentRect;
          setOuterDimensions({
            width: width + 50,
            height: height + 50,
          });
        }
      });
 
      resizeObserver.observe(childRef.current);
 
      return () => {
        resizeObserver.disconnect();
      };
    }
  }, [children]);
 
  return (
    <motion.div
      ref={ref}
      style={{
        transformStyle: "preserve-3d",
        transform,
        width: outerDimensions.width,
        height: outerDimensions.height,
      }}
      className={cn(
        outerBackground,
        "relative flex items-center justify-center rounded-lg"
      )}
    >
      <div
        ref={childRef}
        style={{
          transform: "translateZ(75px)",
          transformStyle: "preserve-3d",
        }}
        className="z-10"
      >
        {children}
      </div>
    </motion.div>
  );
};

    Update the import paths to match your project setup.

    Props

    PropTypeDefaultDescription
    childrenReact.Reac-The content of the card.
    outerBackgroundstring"bg-gradient-to-br from-yellow-500 to-orange-400"The background color of the outer card in tailwind classes.

    Credits

    • Inspired by the cards found on Gitbook.
    • The gif is made by Milo Targett and you can find it on Giphy.