97 lines
3.0 KiB
TypeScript
97 lines
3.0 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useRef } from 'react'
|
||
|
||
export default function DotWaves() {
|
||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||
|
||
useEffect(() => {
|
||
const canvas = canvasRef.current
|
||
if (!canvas) return
|
||
|
||
const ctx = canvas.getContext('2d')
|
||
if (!ctx) return
|
||
|
||
// Устанавливаем размеры canvas равными размерам контейнера
|
||
const resizeCanvas = () => {
|
||
const container = canvas.parentElement
|
||
if (container) {
|
||
canvas.width = container.offsetWidth
|
||
canvas.height = container.offsetHeight
|
||
}
|
||
}
|
||
|
||
resizeCanvas()
|
||
window.addEventListener('resize', resizeCanvas)
|
||
|
||
// Параметры анимации
|
||
const dots: { x: number; y: number; z: number }[] = []
|
||
const numberOfDots = 200 // Больше точек для лучшего эффекта
|
||
const maxDepth = 1000 // Максимальная глубина по Z
|
||
const speed = 10 // Скорость движения
|
||
|
||
// Создаем точки со случайными позициями в 3D пространстве
|
||
for (let i = 0; i < numberOfDots; i++) {
|
||
dots.push({
|
||
x: (Math.random() - 0.5) * canvas.width * 2,
|
||
y: (Math.random() - 0.5) * canvas.height * 2,
|
||
z: Math.random() * maxDepth,
|
||
})
|
||
}
|
||
|
||
// Анимация
|
||
let animationFrameId: number
|
||
const animate = () => {
|
||
ctx.fillStyle = 'rgba(0, 0, 0, 0.2)'
|
||
ctx.fillRect(0, 0, canvas.width, canvas.height)
|
||
|
||
const centerX = canvas.width / 2
|
||
const centerY = canvas.height / 2
|
||
|
||
// Рисуем точки
|
||
dots.forEach((dot) => {
|
||
// Движение к наблюдателю (уменьшаем Z)
|
||
dot.z = dot.z - speed
|
||
|
||
// Если точка слишком близко, перемещаем её обратно вдаль
|
||
if (dot.z <= 1) {
|
||
dot.z = maxDepth
|
||
dot.x = (Math.random() - 0.5) * canvas.width * 2
|
||
dot.y = (Math.random() - 0.5) * canvas.height * 2
|
||
}
|
||
|
||
// Проецируем 3D координаты на 2D экран
|
||
const scale = maxDepth / (maxDepth + dot.z)
|
||
const x2d = centerX + dot.x * scale
|
||
const y2d = centerY + dot.y * scale
|
||
|
||
// Размер и яркость зависят от Z-координаты
|
||
const size = Math.max(0.5, 2 * scale)
|
||
const alpha = Math.min(1, scale * 1.5)
|
||
|
||
ctx.beginPath()
|
||
ctx.arc(x2d, y2d, size, 0, Math.PI * 2)
|
||
ctx.fillStyle = `rgba(255, 255, 255, ${alpha})`
|
||
ctx.fill()
|
||
})
|
||
|
||
animationFrameId = requestAnimationFrame(animate)
|
||
}
|
||
|
||
animate()
|
||
|
||
// Очистка
|
||
return () => {
|
||
window.removeEventListener('resize', resizeCanvas)
|
||
cancelAnimationFrame(animationFrameId)
|
||
}
|
||
}, [])
|
||
|
||
return (
|
||
<canvas
|
||
ref={canvasRef}
|
||
className="absolute inset-0 w-full h-full pointer-events-none"
|
||
style={{ opacity: 0.5 }}
|
||
/>
|
||
)
|
||
} |