AnimationsIntermediateApril 8, 2026
Blurry Cursor
A custom cursor that uses GSAP and linear interpolation (lerp) for a smooth trailing follow effect. When the cursor enters the heading, it expands to 400px and blurs, creating a color-inverted spotlight via mix-blend-mode: difference.
View Full Demo →Preview
Hover
Motion Forward
Design studio
Source
index.jsx
"use client";
import { useEffect, useRef, useState } from "react";
import gsap from "gsap";
import styles from "./styles.module.css";
const lerp = (x, y, a) => x * (1 - a) + y * a;
export default function BlurryCursor() {
const demoRef = useRef(null);
const circleRef = useRef(null);
const mouse = useRef({ x: -200, y: -200 });
const delayed = useRef({ x: -200, y: -200 });
const rafId = useRef(null);
const [isActive, setIsActive] = useState(false);
useEffect(() => {
const el = demoRef.current;
if (!el) return;
// Animate in RAF — lerp delayed position toward real mouse position
const animate = () => {
delayed.current = {
x: lerp(delayed.current.x, mouse.current.x, 0.075),
y: lerp(delayed.current.y, mouse.current.y, 0.075),
};
gsap.set(circleRef.current, {
x: delayed.current.x,
y: delayed.current.y,
xPercent: -50,
yPercent: -50,
});
rafId.current = requestAnimationFrame(animate);
};
const onMove = (e) => {
const rect = el.getBoundingClientRect();
mouse.current = { x: e.clientX - rect.left, y: e.clientY - rect.top };
};
const onLeave = () => {
mouse.current = { x: -200, y: -200 };
setIsActive(false);
};
el.addEventListener("mousemove", onMove);
el.addEventListener("mouseleave", onLeave);
rafId.current = requestAnimationFrame(animate);
return () => {
el.removeEventListener("mousemove", onMove);
el.removeEventListener("mouseleave", onLeave);
cancelAnimationFrame(rafId.current);
};
}, []);
return (
<div ref={demoRef} className={styles.demo}>
{/* Text to hover — triggers cursor expansion */}
<p className={styles.label}>Hover</p>
<h1
className={styles.heading}
onMouseOver={() => setIsActive(true)}
onMouseLeave={() => setIsActive(false)}
>
Motion Forward
</h1>
<p className={styles.sub}>Design studio</p>
{/* Custom cursor — position: absolute within demo container */}
<div
ref={circleRef}
className={`${styles.cursor} ${isActive ? styles.cursorActive : ""}`}
/>
</div>
);
}
styles.module.css
.demo {
position: relative;
height: 100dvh;
background: #0d0d0d;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 0.75rem;
cursor: none;
overflow: hidden;
user-select: none;
}
.label {
font-size: 0.8rem;
letter-spacing: 0.12em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.35);
margin: 0;
position: relative;
z-index: 1;
}
.heading {
font-size: clamp(3rem, 10vw, 8rem);
font-weight: 700;
letter-spacing: -0.04em;
color: #f5f5f5;
margin: 0;
text-align: center;
padding: 0 0.25em;
position: relative;
z-index: 1;
}
.sub {
font-size: 1rem;
color: rgba(255, 255, 255, 0.35);
margin: 0;
position: relative;
z-index: 1;
}
/* ── Cursor circle ── */
.cursor {
position: absolute;
top: 0;
left: 0;
border-radius: 50%;
pointer-events: none;
mix-blend-mode: difference;
background-color: #bce4f2;
width: 30px;
height: 30px;
transition:
width 0.3s ease-out,
height 0.3s ease-out,
filter 0.3s ease-out;
z-index: 10;
/* GSAP controls x/y via transform; top/left stay at 0 */
}
.cursorActive {
width: 400px;
height: 400px;
filter: blur(30px);
}
Dependencies
gsap