'use client'; import React, { HTMLAttributes, useEffect, useRef, useState } from 'react'; import styles from './cursor-trail.module.css'; export interface CursorTrailProps extends HTMLAttributes { trailCount?: number; trailSize?: number; trailColor?: string; trailColorAlt?: string; blendMode?: 'normal' | 'difference' | 'screen' | 'multiply' | 'exclusion'; cursorShape?: 'circle' | 'square' | 'crosshair'; hasCenterDot?: boolean; hoverScale?: number; stagger?: number; } export const CursorTrail = ({ trailCount = 3, trailSize = 20, trailColor = '#fff', trailColorAlt, blendMode = 'difference', hasCenterDot = true, hoverScale = 2, stagger = 50, className = '', ...props }: CursorTrailProps) => { const [mousePos, setMousePos] = useState({ x: 0, y: 0 }); const [isHovering, setIsHovering] = useState(false); const trails = useRef>(Array(trailCount).fill({ x: 0, y: 0 })); const rafRef = useRef(0); const targetRef = useRef({ x: 0, y: 0 }); useEffect(() => { const handleMouseMove = (e: MouseEvent) => { targetRef.current = { x: e.clientX, y: e.clientY }; setIsHovering((e.target as HTMLElement)?.matches?.('a, button, [role="button"], input, select, textarea') ?? false); }; document.addEventListener('mousemove', handleMouseMove); return () => document.removeEventListener('mousemove', handleMouseMove); }, []); useEffect(() => { const animate = () => { setMousePos(targetRef.current); const newTrails = [...trails.current]; newTrails.unshift({ ...targetRef.current }); if (newTrails.length > trailCount) newTrails.pop(); trails.current = newTrails; rafRef.current = requestAnimationFrame(animate); }; rafRef.current = requestAnimationFrame(animate); return () => cancelAnimationFrame(rafRef.current); }, [trailCount]); return (
{trails.current.map((pos, i) => (
))} {hasCenterDot &&
}
); }; export default CursorTrail;