Interactive Particles Canvas
Interactive Particles Canvas component with smooth animations and modern UI.
Installation
Add this component to your project using the CLI:
terminal
npx vui-registry-cli-v1 add interactive-particles-canvasSource Code
interactive-particles-canvas.tsx
'use client'
import React, { useEffect, useRef } from 'react'
import { cn } from '@/lib/utils'
interface Particle {
x: number
y: number
vx: number
vy: number
size: number
color: string
}
export function InteractiveParticlesCanvas() {
const canvasRef = useRef<HTMLCanvasElement>(null)
const containerRef = useRef<HTMLDivElement>(null)
const particles = useRef<Particle[]>([])
const animationFrameId = useRef<number>(0)
const mouse = useRef({ x: 0, y: 0 })
useEffect(() => {
const canvas = canvasRef.current
const container = containerRef.current
if (!canvas || !container) return
const ctx = canvas.getContext('2d')
if (!ctx) return
const resize = () => {
canvas.width = container.clientWidth
canvas.height = container.clientHeight
initParticles()
}
const initParticles = () => {
particles.current = []
const count = Math.floor((canvas.width * canvas.height) / 10000)
for (let i = 0; i < count; i++) {
particles.current.push({
x: Math.random() * canvas.width,
y: Math.random() * canvas.height,
vx: (Math.random() - 0.5) * 0.5,
vy: (Math.random() - 0.5) * 0.5,
size: Math.random() * 2 + 1,
color: `rgba(${Math.floor(Math.random() * 50 + 200)}, ${Math.floor(Math.random() * 50 + 200)}, 255, ${Math.random() * 0.5 + 0.1})`
})
}
}
const animate = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
particles.current.forEach(p => {
p.x += p.vx
p.y += p.vy
// Bounce off walls
if (p.x < 0 || p.x > canvas.width) p.vx *= -1
if (p.y < 0 || p.y > canvas.height) p.vy *= -1
// Mouse interaction
const dx = mouse.current.x - p.x
const dy = mouse.current.y - p.y
const dist = Math.sqrt(dx * dx + dy * dy)
const maxDist = 150
if (dist < maxDist) {
const force = (maxDist - dist) / maxDist
p.vx -= (dx / dist) * force * 0.5
p.vy -= (dy / dist) * force * 0.5
}
// Friction
p.vx *= 0.99
p.vy *= 0.99
// Draw
ctx.beginPath()
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2)
ctx.fillStyle = p.color
ctx.fill()
})
// Draw connections
ctx.strokeStyle = 'rgba(100, 200, 255, 0.05)'
for (let i = 0; i < particles.current.length; i++) {
for (let j = i + 1; j < particles.current.length; j++) {
const p1 = particles.current[i]
const p2 = particles.current[j]
const dx = p1.x - p2.x
const dy = p1.y - p2.y
const dist = Math.sqrt(dx * dx + dy * dy)
if (dist < 100) {
ctx.beginPath()
ctx.moveTo(p1.x, p1.y)
ctx.lineTo(p2.x, p2.y)
ctx.stroke()
}
}
}
animationFrameId.current = requestAnimationFrame(animate)
}
window.addEventListener('resize', resize)
resize()
animate()
return () => {
window.removeEventListener('resize', resize)
if (animationFrameId.current) cancelAnimationFrame(animationFrameId.current)
}
}, [])
const handleMouseMove = (e: React.MouseEvent) => {
if (containerRef.current) {
const rect = containerRef.current.getBoundingClientRect()
mouse.current = {
x: e.clientX - rect.left,
y: e.clientY - rect.top
}
}
}
return (
<div
ref={containerRef}
className="relative w-full h-[400px] bg-neutral-950 overflow-hidden rounded-xl border border-neutral-800"
onMouseMove={handleMouseMove}
>
<canvas ref={canvasRef} className="absolute inset-0" />
<div className="absolute inset-0 pointer-events-none flex items-center justify-center">
<h2 className="text-4xl font-bold text-white/10 select-none tracking-widest">INTERACTIVE</h2>
</div>
</div>
)
}

