Smooth Follower Cursor
An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.
An interactive React component that adds a dynamic bubble effect, visually tracking cursor movement in real time.
1"use client"23import { useState, useEffect, useRef } from "react"45export default function SmoothFollower() {6const mousePosition = useRef({ x: 0, y: 0 })78const dotPosition = useRef({ x: 0, y: 0 })9const borderDotPosition = useRef({ x: 0, y: 0 })1011const [renderPos, setRenderPos] = useState({ dot: { x: 0, y: 0 }, border: { x: 0, y: 0 } })12const [isHovering, setIsHovering] = useState(false)1314const DOT_SMOOTHNESS = 0.215const BORDER_DOT_SMOOTHNESS = 0.11617useEffect(() => {18const handleMouseMove = (e: MouseEvent) => {19mousePosition.current = { x: e.clientX, y: e.clientY }20}2122const handleMouseEnter = () => setIsHovering(true)23const handleMouseLeave = () => setIsHovering(false)2425// Add event listeners26window.addEventListener("mousemove", handleMouseMove)2728const interactiveElements = document.querySelectorAll("a, button, img, input, textarea, select")29interactiveElements.forEach((element) => {30element.addEventListener("mouseenter", handleMouseEnter)31element.addEventListener("mouseleave", handleMouseLeave)32})3334// Animation function for smooth movement35const animate = () => {36const lerp = (start: number, end: number, factor: number) => {37return start + (end - start) * factor38}3940dotPosition.current.x = lerp(dotPosition.current.x, mousePosition.current.x, DOT_SMOOTHNESS)41dotPosition.current.y = lerp(dotPosition.current.y, mousePosition.current.y, DOT_SMOOTHNESS)4243borderDotPosition.current.x = lerp(borderDotPosition.current.x, mousePosition.current.x, BORDER_DOT_SMOOTHNESS)44borderDotPosition.current.y = lerp(borderDotPosition.current.y, mousePosition.current.y, BORDER_DOT_SMOOTHNESS)4546setRenderPos({47dot: { x: dotPosition.current.x, y: dotPosition.current.y },48border: { x: borderDotPosition.current.x, y: borderDotPosition.current.y },49})5051requestAnimationFrame(animate)52}5354// Start animation loop55const animationId = requestAnimationFrame(animate)5657// Clean up58return () => {59window.removeEventListener("mousemove", handleMouseMove)6061interactiveElements.forEach((element) => {62element.removeEventListener("mouseenter", handleMouseEnter)63element.removeEventListener("mouseleave", handleMouseLeave)64})6566cancelAnimationFrame(animationId)67}68}, [])6970if (typeof window === "undefined") return null7172return (73<div className="pointer-events-none fixed inset-0 z-50">74<div75className="absolute rounded-full dark:bg-white bg-black "76style={{77width: "8px",78height: "8px",79transform: "translate(-50%, -50%)",80left: `${renderPos.dot.x}px`,81top: `${renderPos.dot.y}px`,82}}83/>8485<div86className="absolute rounded-full border dark:border-white border-black "87style={{88width: isHovering ? "44px" : "28px",89height: isHovering ? "44px" : "28px",90transform: "translate(-50%, -50%)",91left: `${renderPos.border.x}px`,92top: `${renderPos.border.y}px`,93transition: "width 0.3s, height 0.3s",94}}95/>96</div>97)98}99