← Back to Design

Particle Effects

Performance-First Particles

Visual effects that enhance without degrading. 60fps target. Memory efficient. GPU accelerated when possible.

// Core particle system class ParticleSystem { constructor() { this.canvas = document.createElement('canvas'); this.ctx = this.canvas.getContext('2d'); this.particles = []; this.animationId = null; this.setupCanvas(); } setupCanvas() { this.canvas.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 9998; `; this.resizeCanvas(); window.addEventListener('resize', () => this.resizeCanvas()); } resizeCanvas() { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; } }

Burst Generation

Radial particle distribution. Velocity randomization. Color variety. Size variation.

createBurst(x, y, options = {}) { const defaults = { count: 30, colors: ['#4facfe', '#00f2fe'], speed: 5, life: 2000 }; const config = { ...defaults, ...options }; for (let i = 0; i < config.count; i++) { const angle = (Math.PI * 2 * i) / config.count + Math.random() * 0.3; const speed = config.speed * (0.5 + Math.random() * 0.5); this.particles.push({ x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, color: config.colors[Math.floor(Math.random() * config.colors.length)], size: 2 + Math.random() * 4, life: config.life, maxLife: config.life, decay: 0.98 }); } if (!this.animationId) this.startAnimation(); }

Animation Loop

RequestAnimationFrame based. Particle lifecycle management. Automatic cleanup.

startAnimation() { if (!document.body.contains(this.canvas)) { document.body.appendChild(this.canvas); } const animate = () => { this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); this.particles = this.particles.filter(particle => { // Update physics particle.x += particle.vx; particle.y += particle.vy; particle.vx *= particle.decay; particle.vy *= particle.decay; particle.life -= 16; // ~60fps assumption // Render const alpha = particle.life / particle.maxLife; this.ctx.save(); this.ctx.globalAlpha = alpha; this.ctx.fillStyle = particle.color; this.ctx.beginPath(); this.ctx.arc(particle.x, particle.y, particle.size, 0, Math.PI * 2); this.ctx.fill(); this.ctx.restore(); return particle.life > 0; }); if (this.particles.length > 0) { this.animationId = requestAnimationFrame(animate); } else { this.stopAnimation(); } }; this.animationId = requestAnimationFrame(animate); } stopAnimation() { if (this.animationId) { cancelAnimationFrame(this.animationId); this.animationId = null; } if (this.canvas.parentNode) { this.canvas.parentNode.removeChild(this.canvas); } }

Memory Management

Particle pool reuse. Canvas cleanup. Event listener removal. Garbage collection friendly.

// Efficient particle management class OptimizedParticleSystem extends ParticleSystem { constructor() { super(); this.particlePool = []; this.maxParticles = 500; // Performance limit } getParticle() { return this.particlePool.pop() || {}; } recycleParticle(particle) { // Reset properties Object.keys(particle).forEach(key => delete particle[key]); this.particlePool.push(particle); } createBurst(x, y, options = {}) { // Respect performance limits if (this.particles.length > this.maxParticles) return; const config = { ...this.defaults, ...options }; for (let i = 0; i < Math.min(config.count, this.maxParticles - this.particles.length); i++) { const particle = this.getParticle(); // Initialize reused particle const angle = (Math.PI * 2 * i) / config.count; const speed = config.speed * (0.5 + Math.random() * 0.5); Object.assign(particle, { x, y, vx: Math.cos(angle) * speed, vy: Math.sin(angle) * speed, color: config.colors[Math.floor(Math.random() * config.colors.length)], size: 2 + Math.random() * 4, life: config.life, maxLife: config.life, decay: 0.98 }); this.particles.push(particle); } } }

Integration Points

Logo clicks trigger bursts. Menu reveals get sparkles. Discovery celebrations get explosions.

// Portfolio integration examples const particles = new OptimizedParticleSystem(); // Logo evolution celebration function celebrateLogoEvolution(logoElement) { const rect = logoElement.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; particles.createBurst(x, y, { count: 20, colors: ['#4facfe', '#00f2fe'], speed: 3, life: 1500 }); } // Menu link appearance function enhanceMenuReveal(linkElement) { const rect = linkElement.getBoundingClientRect(); particles.createBurst(rect.right - 10, rect.top + rect.height / 2, { count: 8, colors: ['rgba(255,255,255,0.8)'], speed: 2, life: 1000 }); } // Easter egg discovery function explodeDiscovery(element) { const rect = element.getBoundingClientRect(); particles.createBurst(rect.left + rect.width / 2, rect.top + rect.height / 2, { count: 50, colors: ['#ffd700', '#ff6b6b', '#4ecdc4'], speed: 6, life: 3000 }); }

Performance Monitoring

Frame rate tracking. Particle count limits. Automatic quality adjustment.

// Performance-aware system class SmartParticleSystem extends OptimizedParticleSystem { constructor() { super(); this.frameCount = 0; this.lastFpsCheck = Date.now(); this.currentFps = 60; this.qualityLevel = 1; // 0.5 = half particles, 2 = double } checkPerformance() { this.frameCount++; const now = Date.now(); if (now - this.lastFpsCheck > 1000) { this.currentFps = this.frameCount; this.frameCount = 0; this.lastFpsCheck = now; // Adjust quality based on performance if (this.currentFps < 45) { this.qualityLevel = Math.max(0.5, this.qualityLevel * 0.8); } else if (this.currentFps > 55) { this.qualityLevel = Math.min(2, this.qualityLevel * 1.1); } } } createBurst(x, y, options = {}) { options.count = Math.floor((options.count || 30) * this.qualityLevel); super.createBurst(x, y, options); } }

Built for my portfolio's magic moments. Celebrates discoveries without overwhelming slower devices. Quality scales automatically.