Building a Custom Cursor in Next.js 14

thumbnail

Hello Guys, In this quick tutorial, we'll explore how to create a custom cursor for your Next.js application. Our requirement for this custom cursor was to follow our main cursor and dynamically change its color based on the type of element it hovers over. Using this foundation knowledge you can build your own cursor matching your imagination or requirements.

Prerequisites

Before we start, ensure you have a Next.js project set up.

npx create-next-app@latest

1. Setting Up the Project

Open your project in your code editor and create a new component for the custom cursor under the components folder if you selected to create srcfolder while creating your next js project then the components folder should be under src else place it on your project root. Create a file named CustomCursor.tsxin the components folder and add the following code:

//src/components/custom-cursor.tsx
// Import necessary React hooks and components
import React, {useEffect, useRef, useState } from 'react';
// Define cursor colors
const CURSOR_COLORS = {
  "h1": "green-400",
  "button": "orange-500",
  "default": "sky-500"
};
// Main CustomCursor component
const CustomCursor = () => {
  // Reference to the cursor element
  const cursorRef = useRef(null);
  // State to track cursor position
  const [position, setPosition] = useState({ x: 0, y: 0 });
  // State to track cursor color
  const [cursorColor, setCursorColor] = useState("sky-500");
  // State to track click event
  const [clicked, setClicked] = useState(false);

  useEffect(() => {
    // Event listener for mouse movement
    const handleMouseMove = (e) => {
      setPosition({
        x: e.clientX
        y: e.clientY
      });
    };
    // Event listener for mouse click
    const handleMouseDown = () => {
      setClicked(true);
      // Reset click state after 800 milliseconds
      setTimeout(() => {
        setClicked(false);
      }, 800);
    };
    // Event listener for mouseover (hover) on HTML elements
    const handleMouseOver = (e) => {
      // Get the HTML tag name
      const tagName = e.target.tagName.toLowerCase();
      // Set cursor color based on the tag, default to "sky-500"
      setCursorColor(CURSOR_COLORS[tagName] || CURSOR_COLORS["default"]);
    };

    window.addEventListener("mousemove", handleMouseMove);
    window.addEventListener("mousedown", handleMouseDown);
    window.addEventListener("mouseover", handleMouseOver);
    // Cleanup event listeners on component unmount
    return () => {
      window.removeEventListener("mousemove", handleMouseMove);
      window.removeEventListener("mousedown", handleMouseDown);
      window.removeEventListener("mouseover", handleMouseOver);
    };
  }, []); // useEffect runs only once on mount

  return (
    <>
      <div
        style={{ top: position.y, left: position.x }}
        ref={cursorRef}
        className={`fixed pointer-events-none transition-all -translate-x-1/2 -translate-y-1/2 z-50 ease-in duration-300 rounded-full w-3 h-3 bg-${cursorColor}`}
      />
      <div
        style={{ top: position.y, left: position.x }}
        ref={cursorRef}
        className={`p-0 fixed pointer-events-none transition-all -translate-x-1/2 -translate-y-1/2 z-50 ease-in duration-500 rounded-full w-8 h-8 border-2 border-${cursorColor} `}
      >
        <div
          className={`w-8 h-8 ${clicked ? "scale-100 opacity-30" : "scale-0 opacity-0"} -translate-x-[1px] -translate-y-[1px] rounded-full bg-${cursorColor} ease-in transition-all duration-500 -z-10`}
        />
      </div>
    </>
  );
};

export default CustomCursor;

Let's do the code breakdown.

Starting with imports we need the react hooks
useStateto manage state variables for cursor position, color, and click status. useEffectHandle side effects like setting up event listeners for mouse events.useRef Provides a reference to the cursor element for direct DOM interaction. The CURSOR_COLORS object is defined to map HTML tag names to specific cursor colors, allowing for a dynamic and visually appealing cursor based on the hovered HTML elements, for now, we have just defined elements you can create for other elements as well.

We have 3 state variables positionto track the current cursor position, cursorColorto track the current cursor color and clicked to track the click event for a click effect.

Inside the useEffect hook, we have handleMouseMove function which updates the position state based on the mouse coordinates. The handleMouseDown function triggers a click effect by setting the clicked state to true and resetting it after a short delay of 800ms and handleMouseOver, responsible for dynamically changing the cursor color based on the HTML tag being hovered. It checks the tag name using the e.target.tagName property and assigns a corresponding color from the CURSOR_COLORS object. The useEffect hook also ensures the cleanup of these event listeners when the component unmounts.

Finally, for the JSX, we have 3 divs one div for solid circle, one for outline, and one for showing click effect each styled using tailwindcss. Important styles to note here are below:

  1. fixed: Positions the cursor elements relative to the viewport, making them fixed in place during scrolling.
  2. pointer-events-none: Ensures that the cursor elements do not block or interfere with mouse events, allowing underlying elements to receive them.
  3. -translate-x-1/2 -translate-y-1/2: Translates the cursor elements horizontally and vertically by half of their own width and height, respectively, to center them on the mouse position.
  4. transition, duration-, ease-in: Used for smoothing the cursor movements and changes.

If you want to know more details on styles you can refer to Tailwindcss docs

Installation - Tailwind CSS

2. Integrating the Custom Cursor

Now, let's integrate the CustomCursor component into your Next.js application. Open the layout.tsx file in the app folder and import the CustomCursor component and place it below the body tag:

// app/layouts.tsx

import type { Metadata } from 'next'
import { Karla } from 'next/font/google'
import './globals.css'
import CustomCursor from '@/components/custom-cursor'

const karla = Karla({ subsets: ['latin'], variable: "--font-karla" })

export const metadata: Metadata = {
  title: 'Custom Cursor',
  description: '',
}

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body suppressHydrationWarning={true} className={karla.className}>
        <CustomCursor />{children}</body>
    </html>
  )
}

This modification ensures that the CustomCursor component is rendered on every page of your Next.js application. You can now add h1 tags, buttons, etc.

3. Run Your Application

Start your Next.js application to see the custom cursor in action:

npm run dev

Visit http://localhost:3000 in your browser and hover over different elements to observe the custom cursor adapting its appearance.

Congratulations! You've successfully implemented a custom cursor in your Next.js application. Feel free to customize the styles and behavior further based on your project's requirements.