import { useEffect, useRef, useState, } from 'react' import { isMobileDevice } from 'config' type Particle = { height: number, // Высота частицы image: HTMLImageElement, // Изображение частицы life: number, // Время жизни частицы opacity: number, // Непрозрачность частицы, vx: number, // Горизонтальная скорость частицы vy: number, // Вертикальная скорость частицы width: number, // Ширина частицы x: number, // Cмещение по горизонтали y: number, // Смещение по вертикали } const BIRTH_RATE = 100 const PARTICLES_COUNT = isMobileDevice ? 1 : 2 export const useParticles = () => { const canvasRef = useRef(null) const likeButtonRef = useRef(null) const requestAnimationIdRef = useRef(null) const [generateParticles, setGenerateParticles] = useState(false) useEffect(() => { const canvas = canvasRef.current const likeButton = likeButtonRef.current if (!canvas || !likeButton) return () => {} let intervalId: NodeJS.Timeout const particleImage = new Image() particleImage.src = '/images/like-active-icon.svg' const ctx = canvas.getContext('2d') if (!ctx) return () => {} const canvasRect = canvas.getBoundingClientRect() const likeButtonRect = likeButton.getBoundingClientRect() // Массив для хранения частиц const particles: Array = [] // Начальные координаты вылета частиц const startX = (likeButtonRect.left - canvasRect.left) + likeButtonRect.width / 2 const startY = likeButtonRect.top - canvasRect.top // Функция для создания частиц const createParticles = (count: number) => { for (let i = 0; i < count; i++) { const particleSize = Math.random() * (isMobileDevice ? 30 : 40) const particle: Particle = { height: particleSize, image: particleImage, life: 500, opacity: 0.5, vx: 0, vy: -(Math.random() * 2 + 2), width: particleSize, x: startX - particleSize / 2 + Math.random() * 40 - 20, y: startY, } particles.push(particle) } } // Функция для обновления частиц const updateParticles = () => { ctx.clearRect( 0, 0, canvas.width, canvas.height, ) for (let i = 0; i < particles.length; i++) { const particle = particles[i] particle.life-- // уменьшаем время жизни частицы // Удаляем частицу из массива, если её время жизни истекло if (particle.life <= 0) { particles.splice(i, 1) i-- } else { particle.x += particle.vx // Обновляем положение частицы по горизонтали particle.y += particle.vy // Обновляем положение частицы по вертикали ctx.globalAlpha = particle.opacity ctx.drawImage( particle.image, particle.x, particle.y, particle.width, particle.height, ) } } if (particles.length === 0 && requestAnimationIdRef.current) { cancelAnimationFrame(requestAnimationIdRef.current) return } requestAnimationIdRef.current = requestAnimationFrame(updateParticles) } if (generateParticles) { createParticles(PARTICLES_COUNT) intervalId = setInterval(() => createParticles(PARTICLES_COUNT), BIRTH_RATE) updateParticles() } return () => { intervalId && clearInterval(intervalId) } }, [generateParticles]) useEffect(() => () => { requestAnimationIdRef.current && cancelAnimationFrame(requestAnimationIdRef.current) }, []) return { canvasRef, likeButtonRef, setGenerateParticles, } }