Follow Cursor Effect
An interactive React component that tracks the cursor with a small gray circle, creating a smooth following effect.
An interactive React component that tracks the cursor with a small gray circle, creating a smooth following effect.
1'use client';23import React, { useEffect } from 'react';45interface FollowCursorProps {6color?: string;7zIndex?: number;8}910const FollowCursor: React.FC<FollowCursorProps> = ({11color = '#323232a6',12zIndex,13}) => {14useEffect(() => {15let canvas: HTMLCanvasElement;16let context: CanvasRenderingContext2D | null;17let animationFrame: number;18let width = window.innerWidth;19let height = window.innerHeight;20let cursor = { x: width / 2, y: height / 2 };21const prefersReducedMotion = window.matchMedia(22'(prefers-reduced-motion: reduce)'23);2425class Dot {26position: { x: number; y: number };27width: number;28lag: number;2930constructor(x: number, y: number, width: number, lag: number) {31this.position = { x, y };32this.width = width;33this.lag = lag;34}3536moveTowards(x: number, y: number, context: CanvasRenderingContext2D) {37this.position.x += (x - this.position.x) / this.lag;38this.position.y += (y - this.position.y) / this.lag;39context.fillStyle = color;40context.beginPath();41context.arc(42this.position.x,43this.position.y,44this.width,450,462 * Math.PI47);48context.fill();49context.closePath();50}51}5253const dot = new Dot(width / 2, height / 2, 10, 10);5455const onMouseMove = (e: MouseEvent) => {56cursor.x = e.clientX;57cursor.y = e.clientY;58};5960const onWindowResize = () => {61width = window.innerWidth;62height = window.innerHeight;63if (canvas) {64canvas.width = width;65canvas.height = height;66}67};6869const updateDot = () => {70if (context) {71context.clearRect(0, 0, width, height);72dot.moveTowards(cursor.x, cursor.y, context);73}74};7576const loop = () => {77updateDot();78animationFrame = requestAnimationFrame(loop);79};8081const init = () => {82if (prefersReducedMotion.matches) {83console.log('Reduced motion enabled, cursor effect skipped.');84return;85}8687canvas = document.createElement('canvas');88context = canvas.getContext('2d');89canvas.style.position = 'fixed';90canvas.style.top = '0';91canvas.style.left = '0';92canvas.style.pointerEvents = 'none';93canvas.width = width;94canvas.height = height;95canvas.style.zIndex = zIndex ? zIndex.toString() : '';96document.body.appendChild(canvas);9798window.addEventListener('mousemove', onMouseMove);99window.addEventListener('resize', onWindowResize);100loop();101};102103const destroy = () => {104if (canvas) canvas.remove();105cancelAnimationFrame(animationFrame);106window.removeEventListener('mousemove', onMouseMove);107window.removeEventListener('resize', onWindowResize);108};109110prefersReducedMotion.onchange = () => {111if (prefersReducedMotion.matches) {112destroy();113} else {114init();115}116};117118init();119120return () => {121destroy();122};123}, [color]);124125return null; // This component doesn't render any visible JSX126};127128export default FollowCursor;129