Spaces:
Running
Running
const canvas = document.getElementById('particleCanvas'); | |
const ctx = canvas.getContext('2d'); | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
// Configuration | |
let config = { | |
particleCount: 100, | |
particleSize: 2, | |
speed: 1, | |
connectionDistance: 100, | |
colorMode: 'rainbow', | |
mouseEffect: 'attract', | |
mouseRadius: 150 | |
}; | |
// Mouse position | |
let mouse = { | |
x: canvas.width / 2, | |
y: canvas.height / 2 | |
}; | |
// Particles array | |
let particles = []; | |
// Color modes | |
const colorModes = { | |
rainbow: (particle, time) => { | |
const hue = (time * 0.1 + particle.id * 10) % 360; | |
return `hsl(${hue}, 70%, 50%)`; | |
}, | |
ocean: (particle, time) => { | |
const hue = 180 + Math.sin(time * 0.001 + particle.id * 0.1) * 60; | |
return `hsl(${hue}, 70%, 50%)`; | |
}, | |
fire: (particle, time) => { | |
const hue = Math.sin(time * 0.001 + particle.id * 0.1) * 30; | |
return `hsl(${hue}, 80%, 50%)`; | |
}, | |
monochrome: (particle, time) => { | |
const brightness = 30 + Math.sin(time * 0.001 + particle.id * 0.1) * 20; | |
return `hsl(0, 0%, ${brightness}%)`; | |
}, | |
neon: (particle, time) => { | |
const colors = ['#ff00ff', '#00ffff', '#ffff00', '#ff00aa']; | |
return colors[Math.floor((time * 0.001 + particle.id * 0.1) % colors.length)]; | |
} | |
}; | |
// Particle class | |
class Particle { | |
constructor(id) { | |
this.id = id; | |
this.x = Math.random() * canvas.width; | |
this.y = Math.random() * canvas.height; | |
this.vx = (Math.random() - 0.5) * config.speed; | |
this.vy = (Math.random() - 0.5) * config.speed; | |
this.size = config.particleSize; | |
this.baseSize = config.particleSize; | |
} | |
update() { | |
// Mouse interaction | |
const dx = mouse.x - this.x; | |
const dy = mouse.y - this.y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance < config.mouseRadius) { | |
const force = (1 - distance / config.mouseRadius) * 0.5; | |
if (config.mouseEffect === 'attract') { | |
this.vx += dx * force * 0.01; | |
this.vy += dy * force * 0.01; | |
} else { | |
this.vx -= dx * force * 0.01; | |
this.vy -= dy * force * 0.01; | |
} | |
} | |
// Update position | |
this.x += this.vx * config.speed; | |
this.y += this.vy * config.speed; | |
// Bounce off walls | |
if (this.x < 0 || this.x > canvas.width) { | |
this.vx *= -0.9; | |
this.x = Math.max(0, Math.min(canvas.width, this.x)); | |
} | |
if (this.y < 0 || this.y > canvas.height) { | |
this.vy *= -0.9; | |
this.y = Math.max(0, Math.min(canvas.height, this.y)); | |
} | |
// Apply friction | |
this.vx *= 0.99; | |
this.vy *= 0.99; | |
// Size animation | |
this.size = this.baseSize + Math.sin(Date.now() * 0.001 + this.id) * 0.5; | |
} | |
draw(time) { | |
ctx.fillStyle = colorModes[config.colorMode](this, time); | |
ctx.beginPath(); | |
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2); | |
ctx.fill(); | |
// Glow effect | |
ctx.shadowBlur = 10; | |
ctx.shadowColor = colorModes[config.colorMode](this, time); | |
ctx.fill(); | |
ctx.shadowBlur = 0; | |
} | |
} | |
// Initialize particles | |
function initParticles() { | |
particles = []; | |
for (let i = 0; i < config.particleCount; i++) { | |
particles.push(new Particle(i)); | |
} | |
} | |
// Draw connections between particles | |
function drawConnections() { | |
for (let i = 0; i < particles.length; i++) { | |
for (let j = i + 1; j < particles.length; j++) { | |
const dx = particles[i].x - particles[j].x; | |
const dy = particles[i].y - particles[j].y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
if (distance < config.connectionDistance) { | |
const opacity = 1 - distance / config.connectionDistance; | |
ctx.strokeStyle = `rgba(100, 255, 218, ${opacity * 0.2})`; | |
ctx.lineWidth = opacity * 0.5; | |
ctx.beginPath(); | |
ctx.moveTo(particles[i].x, particles[i].y); | |
ctx.lineTo(particles[j].x, particles[j].y); | |
ctx.stroke(); | |
} | |
} | |
} | |
} | |
// Animation loop | |
function animate() { | |
ctx.fillStyle = 'rgba(10, 10, 10, 0.1)'; | |
ctx.fillRect(0, 0, canvas.width, canvas.height); | |
const time = Date.now(); | |
// Update and draw particles | |
particles.forEach(particle => { | |
particle.update(); | |
particle.draw(time); | |
}); | |
// Draw connections | |
drawConnections(); | |
requestAnimationFrame(animate); | |
} | |
// Event listeners | |
window.addEventListener('resize', () => { | |
canvas.width = window.innerWidth; | |
canvas.height = window.innerHeight; | |
}); | |
canvas.addEventListener('mousemove', (e) => { | |
mouse.x = e.clientX; | |
mouse.y = e.clientY; | |
}); | |
canvas.addEventListener('click', (e) => { | |
// Create explosion effect | |
for (let i = 0; i < 20; i++) { | |
const angle = (Math.PI * 2 * i) / 20; | |
const speed = 5 + Math.random() * 5; | |
const particle = new Particle(particles.length); | |
particle.x = e.clientX; | |
particle.y = e.clientY; | |
particle.vx = Math.cos(angle) * speed; | |
particle.vy = Math.sin(angle) * speed; | |
particle.baseSize = 3 + Math.random() * 2; | |
particles.push(particle); | |
} | |
// Remove excess particles | |
if (particles.length > config.particleCount * 2) { | |
particles.splice(0, particles.length - config.particleCount); | |
} | |
}); | |
// Keyboard controls | |
document.addEventListener('keydown', (e) => { | |
switch(e.key.toLowerCase()) { | |
case ' ': | |
e.preventDefault(); | |
const modes = Object.keys(colorModes); | |
const currentIndex = modes.indexOf(config.colorMode); | |
config.colorMode = modes[(currentIndex + 1) % modes.length]; | |
break; | |
case '1': | |
config.particleCount = 50; | |
initParticles(); | |
break; | |
case '2': | |
config.particleCount = 100; | |
initParticles(); | |
break; | |
case '3': | |
config.particleCount = 200; | |
initParticles(); | |
break; | |
case '4': | |
config.particleCount = 300; | |
initParticles(); | |
break; | |
case '5': | |
config.particleCount = 500; | |
initParticles(); | |
break; | |
case 'q': | |
config.speed = Math.max(0.1, config.speed - 0.1); | |
break; | |
case 'w': | |
config.speed = Math.min(5, config.speed + 0.1); | |
break; | |
case 'a': | |
config.particleSize = Math.max(1, config.particleSize - 0.5); | |
particles.forEach(p => p.baseSize = config.particleSize); | |
break; | |
case 's': | |
config.particleSize = Math.min(10, config.particleSize + 0.5); | |
particles.forEach(p => p.baseSize = config.particleSize); | |
break; | |
case 'z': | |
config.connectionDistance = Math.max(50, config.connectionDistance - 10); | |
break; | |
case 'x': | |
config.connectionDistance = Math.min(300, config.connectionDistance + 10); | |
break; | |
case 'r': | |
config = { | |
particleCount: 100, | |
particleSize: 2, | |
speed: 1, | |
connectionDistance: 100, | |
colorMode: 'rainbow', | |
mouseEffect: 'attract', | |
mouseRadius: 150 | |
}; | |
initParticles(); | |
break; | |
case 'm': | |
config.mouseEffect = config.mouseEffect === 'attract' ? 'repel' : 'attract'; | |
break; | |
} | |
}); | |
// Initialize and start | |
initParticles(); | |
animate(); |