Initial commit
This commit is contained in:
94
src/components/FloatingParticles.tsx
Normal file
94
src/components/FloatingParticles.tsx
Normal file
@@ -0,0 +1,94 @@
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useReducedMotion } from '../hooks/useReducedMotion'
|
||||
|
||||
interface Particle {
|
||||
x: number
|
||||
y: number
|
||||
size: number
|
||||
speedX: number
|
||||
speedY: number
|
||||
opacity: number
|
||||
color: string
|
||||
}
|
||||
|
||||
export default function FloatingParticles() {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const reducedMotion = useReducedMotion()
|
||||
|
||||
useEffect(() => {
|
||||
if (reducedMotion) return
|
||||
|
||||
const canvas = canvasRef.current
|
||||
if (!canvas) return
|
||||
|
||||
const ctx = canvas.getContext('2d')
|
||||
if (!ctx) return
|
||||
|
||||
let animationId: number
|
||||
let particles: Particle[] = []
|
||||
|
||||
const resize = () => {
|
||||
canvas.width = window.innerWidth
|
||||
canvas.height = window.innerHeight
|
||||
}
|
||||
resize()
|
||||
window.addEventListener('resize', resize)
|
||||
|
||||
const colors = ['rgba(255, 45, 122, 0.15)', 'rgba(255, 107, 157, 0.12)', 'rgba(255, 182, 193, 0.1)']
|
||||
|
||||
const createParticles = () => {
|
||||
particles = []
|
||||
const count = Math.min(Math.floor(window.innerWidth / 15), 80)
|
||||
for (let i = 0; i < count; i++) {
|
||||
particles.push({
|
||||
x: Math.random() * canvas.width,
|
||||
y: Math.random() * canvas.height,
|
||||
size: Math.random() * 3 + 1,
|
||||
speedX: (Math.random() - 0.5) * 0.3,
|
||||
speedY: (Math.random() - 0.5) * 0.3,
|
||||
opacity: Math.random() * 0.5 + 0.1,
|
||||
color: colors[Math.floor(Math.random() * colors.length)],
|
||||
})
|
||||
}
|
||||
}
|
||||
createParticles()
|
||||
|
||||
const animate = () => {
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height)
|
||||
|
||||
particles.forEach((p) => {
|
||||
p.x += p.speedX
|
||||
p.y += p.speedY
|
||||
|
||||
if (p.x < 0) p.x = canvas.width
|
||||
if (p.x > canvas.width) p.x = 0
|
||||
if (p.y < 0) p.y = canvas.height
|
||||
if (p.y > canvas.height) p.y = 0
|
||||
|
||||
ctx.beginPath()
|
||||
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2)
|
||||
ctx.fillStyle = p.color
|
||||
ctx.fill()
|
||||
})
|
||||
|
||||
animationId = requestAnimationFrame(animate)
|
||||
}
|
||||
|
||||
animate()
|
||||
|
||||
return () => {
|
||||
cancelAnimationFrame(animationId)
|
||||
window.removeEventListener('resize', resize)
|
||||
}
|
||||
}, [reducedMotion])
|
||||
|
||||
if (reducedMotion) return null
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="fixed inset-0 z-0 pointer-events-none"
|
||||
style={{ opacity: 0.6 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user