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.