Follow Cursor Effect
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 React, { useEffect } from 'react';45interface FollowCursorProps {6color?: string;7}89const FollowCursor: React.FC<FollowCursorProps> = ({ color = '#323232a6' }) => {10useEffect(() => {11let canvas: HTMLCanvasElement;12let context: CanvasRenderingContext2D | null;13let animationFrame: number;14let width = window.innerWidth;15let height = window.innerHeight;16let cursor = { x: width / 2, y: height / 2 };17const prefersReducedMotion = window.matchMedia(18'(prefers-reduced-motion: reduce)'19);2021class Dot {22position: { x: number; y: number };23width: number;24lag: number;2526constructor(x: number, y: number, width: number, lag: number) {27this.position = { x, y };28this.width = width;29this.lag = lag;30}3132moveTowards(x: number, y: number, context: CanvasRenderingContext2D) {33this.position.x += (x - this.position.x) / this.lag;34this.position.y += (y - this.position.y) / this.lag;35context.fillStyle = color;36context.beginPath();37context.arc(38this.position.x,39this.position.y,40this.width,410,422 * Math.PI43);44context.fill();45context.closePath();46}47}4849const dot = new Dot(width / 2, height / 2, 10, 10);5051const onMouseMove = (e: MouseEvent) => {52cursor.x = e.clientX;53cursor.y = e.clientY;54};5556const onWindowResize = () => {57width = window.innerWidth;58height = window.innerHeight;59if (canvas) {60canvas.width = width;61canvas.height = height;62}63};6465const updateDot = () => {66if (context) {67context.clearRect(0, 0, width, height);68dot.moveTowards(cursor.x, cursor.y, context);69}70};7172const loop = () => {73updateDot();74animationFrame = requestAnimationFrame(loop);75};7677const init = () => {78if (prefersReducedMotion.matches) {79console.log('Reduced motion enabled, cursor effect skipped.');80return;81}8283canvas = document.createElement('canvas');84context = canvas.getContext('2d');85canvas.style.position = 'fixed';86canvas.style.top = '0';87canvas.style.left = '0';88canvas.style.pointerEvents = 'none';89canvas.width = width;90canvas.height = height;91document.body.appendChild(canvas);9293window.addEventListener('mousemove', onMouseMove);94window.addEventListener('resize', onWindowResize);95loop();96};9798const destroy = () => {99if (canvas) canvas.remove();100cancelAnimationFrame(animationFrame);101window.removeEventListener('mousemove', onMouseMove);102window.removeEventListener('resize', onWindowResize);103};104105prefersReducedMotion.onchange = () => {106if (prefersReducedMotion.matches) {107destroy();108} else {109init();110}111};112113init();114115return () => {116destroy();117};118}, [color]);119120return null; // This component doesn't render any visible JSX121};122123export default FollowCursor;124