import React, { useEffect, useRef, useCallback, useMemo } from 'react'; const ParticleEffect = () => { const containerRef = useRef(null); const particleId = useRef(0); const colors = useMemo(() => [ '#4caf50', '#81c784', '#a5d6a7', '#c8e6c9', '#66bb6a', '#8bc34a', '#cddc39', '#ffeb3b', '#ffc107', '#ff9800', '#ff5722', '#e91e63', '#9c27b0', '#673ab7', '#3f51b5', '#2196f3', '#03a9f4', '#00bcd4', '#009688', '#4caf50', ], []); const createParticle = useCallback((x, y) => { if (!containerRef.current) return; const particleCount = Math.random() * 8 + 6; for (let i = 0; i < particleCount; i++) { const particle = document.createElement('div'); particle.className = 'click-particle'; particle.id = `particle-${particleId.current++}`; const color = colors[Math.floor(Math.random() * colors.length)]; const size = Math.random() * 8 + 4; const angle = (Math.PI * 2 * i) / particleCount + Math.random() * 0.5; const distance = Math.random() * 100 + 50; const dx = Math.cos(angle) * distance; const dy = Math.sin(angle) * distance - Math.random() * 50; particle.style.cssText = ` position: absolute; left: ${x - size / 2}px; top: ${y - size / 2}px; width: ${size}px; height: ${size}px; background: ${color}; border-radius: 50%; pointer-events: none; box-shadow: 0 0 6px ${color}40; --dx: ${dx}px; --dy: ${dy}px; animation: particleAnimation 1.2s ease-out forwards; z-index: 9999; `; containerRef.current.appendChild(particle); setTimeout(() => { if (particle && particle.parentNode) { particle.parentNode.removeChild(particle); } }, 1200); } }, [colors]); const createRipple = useCallback((x, y) => { if (!containerRef.current) return; const ripple = document.createElement('div'); ripple.className = 'click-ripple'; ripple.id = `ripple-${particleId.current++}`; const color = colors[Math.floor(Math.random() * colors.length)]; ripple.style.cssText = ` position: absolute; left: ${x}px; top: ${y}px; width: 0; height: 0; border-radius: 50%; background: radial-gradient(circle, ${color}20 0%, transparent 70%); border: 2px solid ${color}40; pointer-events: none; animation: rippleAnimation 0.8s ease-out forwards; z-index: 9998; transform: translate(-50%, -50%); `; if (!document.querySelector('#ripple-animation-style')) { const style = document.createElement('style'); style.id = 'ripple-animation-style'; style.textContent = ` @keyframes rippleAnimation { 0% { width: 0; height: 0; opacity: 1; } 100% { width: 100px; height: 100px; opacity: 0; } } `; document.head.appendChild(style); } containerRef.current.appendChild(ripple); setTimeout(() => { if (ripple && ripple.parentNode) { ripple.parentNode.removeChild(ripple); } }, 800); }, [colors]); const handleClick = useCallback((event) => { createParticle(event.clientX, event.clientY); createRipple(event.clientX, event.clientY); }, [createParticle, createRipple]); useEffect(() => { document.addEventListener('click', handleClick); if (!document.querySelector('#particle-animation-style')) { const style = document.createElement('style'); style.id = 'particle-animation-style'; style.textContent = ` @keyframes particleAnimation { 0% { transform: translate(0, 0) scale(1); opacity: 1; } 100% { transform: translate(var(--dx), var(--dy)) scale(0); opacity: 0; } } `; document.head.appendChild(style); } return () => { document.removeEventListener('click', handleClick); document.querySelector('#particle-animation-style')?.remove(); document.querySelector('#ripple-animation-style')?.remove(); }; }, [handleClick]); return (
); }; export default ParticleEffect;