Components
Cursor

Cursor Follower

The Cursor component creates a customizable animated cursor that follows the user's mouse movement, with an interactive scaling effect based on the type of element being hovered over.

Installation

npx v3cn add cursor
👾

Make sure that you have installed your modules first and followed the introduction.

Utility File

// Util/cn.ts
 
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(...inputs));
}

Usage

// Import the Component
import { Cursor } from "@/components/ui/cursor.tsx";
 
// Pass custom styles easily
<Cursor cursorClass="border-purple-500 hidden md:inline-block" />;

Cursor Component Properties

Prop NameTypeDescriptionDefault Value
cursorClassstringAdditional tailwind CSS classes for customizing the cursor appearance and behavior.""

Features

  • Follow Cursor: The custom cursor follows the user's mouse movement.
  • Smooth Animation: Follows the mouse smoothly, and reacts to click events with a slight shrinking effect.

How to Use the Component

  1. Import the Cursor component into your project:

    import { Cursor } from "@/components/ui/cursor.tsx";
  2. Add the component in your layout or any component where you want the custom cursor:

    <Cursor cursorClass="border-purple-500 hidden md:inline-block" />
  3. Customize the cursorClass prop for specific styling or visual customization.

Code

"use client";
import { cn } from "@/utils/cn";
import { useState, useEffect, useRef } from "react";
import { motion } from "framer-motion";
 
type cursorProp = {
  cursorClass?: string;
};
export const Cursor = ({ cursorClass }: cursorProp) => {
  const [isInteracting, setIsInteracting] = useState(false);
  const [isClicked, setIsClicked] = useState(false);
 
  const cursorRef = useRef(null);
  const animateTrailer = (e: any) => {
    const x =
      e.clientX - (cursorRef.current as unknown as HTMLElement).offsetWidth / 2;
    const y =
      e.clientY -
      (cursorRef.current as unknown as HTMLElement).offsetHeight / 2;
 
    const keyframes = {
      transform: `translate(${x}px, ${y}px) scale(${isInteracting ? 3 : 1})`,
    };
 
    (cursorRef.current as unknown as HTMLElement)?.animate(keyframes, {
      duration: 100,
      fill: "forwards",
    });
  };
 
  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      const interactable = (e.target as HTMLElement).closest(".interactable");
      const interacting = interactable !== null;
 
      animateTrailer(e);
 
      setIsInteracting(interacting);
    };
 
    window.addEventListener("mousemove", handleMouseMove);
 
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
    };
  }, [isInteracting]);
  useEffect(() => {
    const handleClick = () => {
      setIsClicked(true);
      setTimeout(() => {
        setIsClicked(false);
      }, 100);
    };
 
    window.addEventListener("click", handleClick);
 
    return () => {
      window.removeEventListener("click", handleClick);
    };
  }, []);
  return (
    <>
      <motion.div
        whileTap={{ scale: 0.9 }}
        id="trailer"
        style={{ top: `0px`, left: `0px` }}
        className={cn(
          "bg-transparent rounded-full fixed z-50 pointer-events-none border-[3px] border-slate-500 border-solid w-10 h-10 transition-all",
          isClicked && "w-8 h-8",
          cursorClass
        )}
        ref={cursorRef}
      ></motion.div>
    </>
  );
};