How I Built a Custom Cursor
The Idea 💡
I was looking to add a bit of character to my site. The default browser cursor was... fine, but I wanted something more engaging. So, I set out to build a custom cursor that could add a subtle layer of interactivity.
Step 1: Setting Up the Component
I started by creating a new CustomCursor
component in React. Using styled-components
, I defined a circular cursor with a border:
const StyledCursor = styled.div`
position: fixed;
width: 25px;
height: 25px;
border: 1.5px solid var(--cursor-border);
border-radius: 50%;
pointer-events: none;
transform: translate(-50%, -50%);
z-index: 9999;
`;
Step 2: Adding a Custom SVG Icon
To give the cursor some personality, I added a custom SVG icon. I created a separate IconCursor
component:
import React from 'react';
const IconCursor = (props) => (
<svg
xmlns="http://www.w3.org/2000/svg"
role="img"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="feather feather-cursor"
{...props}
>
<title>Cursor</title>
<circle cx="12" cy="12" r="11" />
<circle cx="12" cy="12" r="1" fill="currentColor" />
</svg>
);
export default IconCursor;
This icon was integrated into the main cursor component for that extra bit of flair:
<StyledCursor ref={cursorRef}>
<IconCursor />
</StyledCursor>
Step 3: Smooth Animations with GSAP
To make the cursor feel fluid, I used GSAP
for smooth animations. The cursor smoothly follows the mouse pointer, giving it a natural feel:
gsap.to(cursorRef.current, {
x,
y,
duration: 0.6,
ease: 'power2.out',
});
Step 4: Adding a Trail Effect
I decided to take it a step further by adding a trail effect, making it look like the cursor leaves a subtle glow behind. I used multiple trail segments for this:
const TrailSegment = styled.div`
position: fixed;
width: 25px;
height: 25px;
background: var(--cursor-background);
border-radius: 50%;
pointer-events: none;
`;
Each trail segment updates its position slightly delayed, creating a trailing effect:
const updateTrail = () => {
trailRefs.current.forEach((ref, index) => {
const delay = (index + 1) * 0.05;
gsap.to(ref.current, {
x: mousePosition.current.x,
y: mousePosition.current.y,
duration: 0.4,
delay,
opacity: isMoving ? 1 - index / trailLength : 0,
});
});
};
Step 5: Adding Interactivity
To make it more dynamic, I added animations for when the cursor hovers over links or when the mouse is pressed:
const handleMouseEnterLink = () => {
gsap.to(cursorRef.current, {
width: 50,
height: 50,
borderColor: 'var(--cursor-link-hover-border)',
boxShadow: '0 0 25px var(--cursor-link-hover-shadow)',
});
};
Step 6: Toggle Feature
Finally, I added a toggle feature so users can enable or disable the custom cursor based on their preference:
export const toggleCursor = (setCursorEnabled) => {
setCursorEnabled((prev) => {
document.body.classList.toggle('cursor-enabled', !prev);
return !prev;
});
};
Wrapping Up
This was a fun way to add a bit of flair to my site, making it more interactive without being overwhelming. It’s those small touches that can enhance user experience.