You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
138 lines
4.1 KiB
138 lines
4.1 KiB
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<HTMLCanvasElement>(null)
|
|
const likeButtonRef = useRef<HTMLButtonElement>(null)
|
|
const requestAnimationIdRef = useRef<number | null>(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<Particle> = []
|
|
|
|
// Начальные координаты вылета частиц
|
|
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,
|
|
}
|
|
}
|
|
|