Neon 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, { useReducer, useEffect } from 'react';45// Define the shape of a ripple object6interface Ripple {7id: string;8x: number;9y: number;10}1112// Props for the RippleCursor component13interface RippleCursorProps {14maxSize?: number; // Maximum size of the ripple15duration?: number; // Duration of the ripple animation in milliseconds16blur?: boolean; // Whether the ripple has a blur effect17}1819// Type for the reducer's state20type RippleState = Ripple[];2122// Type for the reducer's actions23type RippleAction =24| { type: 'ADD_RIPPLE'; payload: Ripple }25| { type: 'REMOVE_RIPPLE'; payload: string };2627// Reducer function28const rippleReducer = (29state: RippleState,30action: RippleAction31): RippleState => {32switch (action.type) {33case 'ADD_RIPPLE':34return [...state, action.payload].slice(-30); // Limit ripple count35case 'REMOVE_RIPPLE':36return state.filter((ripple) => ripple.id !== action.payload);37default:38return state;39}40};4142// Component definition43const RippleCursor: React.FC<RippleCursorProps> = ({44maxSize = 50,45duration = 1000,46blur = true,47}) => {48const [ripples, dispatch] = useReducer(rippleReducer, []);4950// Event handler for mouse movements51const handleMouseMove = (e: MouseEvent): void => {52const ripple: Ripple = {53id: `${Date.now()}-${Math.random()}`,54x: e.clientX,55y: e.clientY,56};5758dispatch({ type: 'ADD_RIPPLE', payload: ripple });5960// Remove ripple after the animation duration61setTimeout(() => {62dispatch({ type: 'REMOVE_RIPPLE', payload: ripple.id });63}, duration);64};6566// Effect to attach and detach the mousemove event listener67useEffect(() => {68window.addEventListener('mousemove', handleMouseMove);6970return () => {71window.removeEventListener('mousemove', handleMouseMove);72};73}, [duration]);7475return (76<div className='fixed top-0 left-0 w-screen h-screen pointer-events-none overflow-hidden z-[9999]'>77{ripples.map((ripple) => (78<div79key={ripple.id}80className='absolute rounded-full bg-blue-500 bg-opacity-50 shadow-[0_0_10px_rgba(0,150,255,0.7),0_0_20px_rgba(0,150,255,0.4)] animate-ripple'81style={{82left: `${ripple.x}px`,83top: `${ripple.y}px`,84width: `${maxSize}px`,85height: `${maxSize}px`,86animationDuration: `${duration}ms`,87filter: blur ? 'blur(4px)' : 'none',88}}89/>90))}91</div>92);93};9495export default RippleCursor;96