Max Particles
The maxParticles configuration option is your primary tool for balancing visual quality with performance. It sets a hard limit on the number of particles that can be created, directly impacting frame rate, CPU usage, and visual density. Understanding how to use this option effectively is crucial for creating performant particle text effects across all devices.
Overview
Section titled “Overview”maxParticles caps the total number of particles rendered on the canvas. When the particle generation process reaches this limit, it stops creating new particles, even if the text rendering could produce more.
const instance = initParticleJS('#canvas', {text: 'PERFORMANCE',colors: ['#695aa6'],maxParticles: 2000 // Limit to 2000 particles});
// Without maxParticles, complex text might generate 8000+ particles// With maxParticles: 2000, it stops at 2000// Result: 3-4x better performance, slightly less detailDefault Value
Section titled “Default Value”// Default maxParticles valueconst instance = initParticleJS('#canvas', {text: 'DEFAULT',colors: ['#695aa6']// maxParticles: 5000 (default)});
// Explicitly set to a different valueconst instance = initParticleJS('#canvas', {text: 'CUSTOM',colors: ['#695aa6'],maxParticles: 1500 // Override default});Default: 5000 particles
This default balances quality and performance for modern desktop browsers. For most use cases, you’ll want to override this based on your specific needs.
How It Works
Section titled “How It Works”Particle Generation Process
Section titled “Particle Generation Process”// Simplified particle generation logicfunction generateParticles(text, maxParticles) {const particles = [];
// Draw text to hidden canvascontext.fillText(text, x, y);
// Sample pixels to create particlesfor (let y = 0; y < height; y += samplingRate) { for (let x = 0; x < width; x += samplingRate) { const pixel = context.getImageData(x, y, 1, 1).data;
if (pixel[3] > 128) { // If pixel is opaque enough particles.push(createParticle(x, y));
// Stop when limit reached if (particles.length >= maxParticles) { return particles; // Early exit } } }}
return particles;}Key behaviors:
- Particles are created by sampling pixels from rendered text
- Generation stops immediately when
maxParticlesis reached - Lower values = faster initialization
- Result: predictable performance regardless of text complexity
Performance Impact
Section titled “Performance Impact”CPU Usage by Particle Count
Section titled “CPU Usage by Particle Count”// Low particle count (500-1000)// CPU: ~5-10% on modern desktop// Mobile: ~15-20%// Frame rate: Consistent 60 FPSconst lowPerf = initParticleJS('#canvas', {text: 'LOW',colors: ['#695aa6'],maxParticles: 800});
// Medium particle count (1500-2500)// CPU: ~15-25% on modern desktop// Mobile: ~30-40%// Frame rate: 50-60 FPS desktop, 30-50 FPS mobileconst medPerf = initParticleJS('#canvas', {text: 'MEDIUM',colors: ['#695aa6'],maxParticles: 2000});
// High particle count (4000-5000)// CPU: ~30-50% on modern desktop// Mobile: 50-80% (may struggle)// Frame rate: 40-60 FPS desktop, 20-40 FPS mobileconst highPerf = initParticleJS('#canvas', {text: 'HIGH',colors: ['#695aa6'],maxParticles: 5000});Performance Measurements
Section titled “Performance Measurements”| Particle Count | Desktop CPU | Mobile CPU | Desktop FPS | Mobile FPS | Initialization |
|---|---|---|---|---|---|
| 500 | 5-10% | 15-20% | 60 | 60 | <50ms |
| 1000 | 10-15% | 20-30% | 60 | 50-60 | 50-100ms |
| 2000 | 15-25% | 30-40% | 50-60 | 30-50 | 100-200ms |
| 3000 | 20-35% | 40-60% | 45-55 | 25-40 | 150-300ms |
| 5000 | 30-50% | 50-80% | 40-60 | 20-40 | 250-500ms |
Tested on: Desktop (M1 MacBook Pro), Mobile (iPhone 12)
Visual Quality vs Performance
Section titled “Visual Quality vs Performance”The Trade-off
Section titled “The Trade-off”// Sparse particles: Fast but less detailedconst sparse = initParticleJS('#canvas', {text: 'SPARSE',colors: ['#695aa6'],maxParticles: 500,fontSize: 80});// Result: Clear letters but grainy texture// Good for: Subtle backgrounds, decorative elements
// Balanced particles: Good detail and performanceconst balanced = initParticleJS('#canvas', {text: 'BALANCED',colors: ['#695aa6'],maxParticles: 2000,fontSize: 80});// Result: Smooth text with good readability// Good for: Hero sections, main content
// Dense particles: Maximum detail but slowerconst dense = initParticleJS('#canvas', {text: 'DENSE',colors: ['#695aa6'],maxParticles: 5000,fontSize: 80});// Result: Crisp, detailed text// Good for: Showcases, landing pages (desktop only)Compensating for Lower Particle Counts
Section titled “Compensating for Lower Particle Counts”You can maintain visual quality with fewer particles:
// Strategy 1: Increase particle sizeconst instance = initParticleJS('#canvas', {text: 'LARGER',colors: ['#695aa6'],maxParticles: 1000, // Lower countparticleRadius: { xs: { base: 3, rand: 1 }, // Larger particles (default: 2) lg: { base: 3, rand: 1 }}});// Fewer particles but they cover more area// Result: Smooth appearance, better performance
// Strategy 2: Increase font sizeconst instance2 = initParticleJS('#canvas', {text: 'BIG',colors: ['#695aa6'],maxParticles: 1200,fontSize: 120 // Larger text (default: 80)});// Particles spread over larger area// Result: Better particle density perception
// Strategy 3: Shorter textconst instance3 = initParticleJS('#canvas', {text: 'OK', // Short text (2 characters vs 8)colors: ['#695aa6'],maxParticles: 1000});// Same particle budget for fewer letters// Result: Very dense, high-quality renderingDevice-Specific Recommendations
Section titled “Device-Specific Recommendations”Mobile Devices
Section titled “Mobile Devices”// Detect mobile deviceconst isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
const instance = initParticleJS('#canvas', {text: 'MOBILE',colors: ['#695aa6'],maxParticles: isMobile ? 1000 : 3000, // 1/3 particles on mobile
// Also increase particle size on mobileparticleRadius: isMobile ? { xs: { base: 3, rand: 1 }} : { xs: { base: 2, rand: 1 }}});
// Recommended mobile ranges:// - Background effects: 500-800// - Hero sections: 1000-1500// - Showcase (if needed): 1500-2000 maxLow-End Devices
Section titled “Low-End Devices”// Detect low-end devicesconst isLowEnd = /iPhone [1-7]|Android [1-5]|iPad [1-4]/i.test(navigator.userAgent);
const instance = initParticleJS('#canvas', {text: 'LOWEND',colors: ['#695aa6'],maxParticles: isLowEnd ? 500 : 2500,
// Enable slow browser detectionrenderTimeThreshold: isLowEnd ? 8 : 16,
// Larger particlesparticleRadius: { xs: { base: isLowEnd ? 4 : 2, rand: 1 }}});
// Low-end device recommendations:// - Background: 300-500// - Hero: 500-800// - Avoid showcases on low-end devicesDesktop Devices
Section titled “Desktop Devices”// Detect high-end desktopconst isHighEnd = window.innerWidth >= 1920 && !(/iPhone|iPad|iPod|Android/i.test(navigator.userAgent));
const instance = initParticleJS('#canvas', {text: 'DESKTOP',colors: ['#695aa6'],maxParticles: isHighEnd ? 5000 : 3000,
fontSize: isHighEnd ? 120 : 80,
explosionRadius: { xs: isHighEnd ? 100 : 80, lg: isHighEnd ? 150 : 120}});
// Desktop recommendations:// - Background: 1500-2500// - Hero: 2500-3500// - Showcase: 4000-5000// - 4K displays: Up to 8000 (with testing)Adaptive Configuration
Section titled “Adaptive Configuration”function getMaxParticlesForDevice() {const ua = navigator.userAgent;const width = window.innerWidth;
// Check for low-end mobileif (/iPhone [1-7]|Android [1-5]/.test(ua)) { return 500;}
// Check for mobileif (/iPhone|iPad|iPod|Android/i.test(ua)) { return 1200;}
// Check for tabletif (/iPad|Android/i.test(ua) && width >= 768) { return 2000;}
// Check for laptopif (width < 1440) { return 2500;}
// Check for desktopif (width < 1920) { return 3500;}
// High-end desktop / 4Kreturn 5000;}
const instance = initParticleJS('#canvas', {text: 'ADAPTIVE',colors: ['#695aa6'],maxParticles: getMaxParticlesForDevice()});Use Case Recommendations
Section titled “Use Case Recommendations”Background Effects
Section titled “Background Effects”Subtle, non-distracting background animations:
const instance = initParticleJS('#canvas', {text: 'BACKGROUND',colors: ['#695aa6'],maxParticles: 800, // Low count for efficiency
// Large particles for visibilityparticleRadius: { xs: { base: 3, rand: 1 }},
// Minimal interactionexplosionRadius: { xs: 60, lg: 80},
// Faster settlingparticleFriction: 0.98});
// Recommended range: 500-1000 particles// Priority: Performance over visual densityHero Sections
Section titled “Hero Sections”Main focal point with good balance:
const instance = initParticleJS('#canvas', {text: 'HERO',colors: ['#695aa6'],maxParticles: 2500, // Balanced count
// Standard particlesparticleRadius: { xs: { base: 2, rand: 1 }},
// Good interactionexplosionRadius: { xs: 80, lg: 120},
// Responsive to cursorparticleFriction: 0.95});
// Recommended range: 2000-3000 particles// Priority: Balance quality and performanceShowcase / Portfolio
Section titled “Showcase / Portfolio”Maximum visual impact for portfolio pieces:
const instance = initParticleJS('#canvas', {text: 'SHOWCASE',colors: ['#695aa6'],maxParticles: 4500, // High count for detail
// Fine particlesparticleRadius: { xs: { base: 2, rand: 0.5 }},
// Dramatic interactionexplosionRadius: { xs: 100, lg: 150},
// Smooth physicsparticleFriction: 0.92});
// Recommended range: 4000-5000 particles// Priority: Visual quality (desktop only)// Note: Consider lazy loading for performanceLogo / Branding
Section titled “Logo / Branding”Clear, recognizable branding element:
const instance = initParticleJS('#canvas', {text: 'LOGO',colors: ['#695aa6'],maxParticles: 1500, // Moderate count
// Larger font for impactfontSize: 100,
// Medium particlesparticleRadius: { xs: { base: 2.5, rand: 1 }},
// Moderate interactionexplosionRadius: { xs: 70, lg: 100}});
// Recommended range: 1000-2000 particles// Priority: Clarity and brand recognitionCall-to-Action
Section titled “Call-to-Action”Attention-grabbing interactive element:
const instance = initParticleJS('#canvas', {text: 'CLICK ME',colors: ['#FF6B6B', '#4ECDC4'],maxParticles: 2000, // Good density
// Start animation on hover onlyautoAnimate: false,trackCursorOnlyInsideCanvas: true,
// Dramatic explosionexplosionRadius: { xs: 100, lg: 150}});
const canvas = document.getElementById('canvas');canvas.addEventListener('mouseenter', () => {instance.startAnimation();});
canvas.addEventListener('mouseleave', () => {instance.destroy();});
// Recommended range: 1500-2500 particles// Priority: Responsive interactionChoosing the Right Value
Section titled “Choosing the Right Value”Decision Matrix
Section titled “Decision Matrix”// Ask these questions:
// 1. What's the primary device?const targetDevice = 'mobile'; // 'mobile', 'tablet', 'desktop'
// 2. What's the text length?const textLength = text.length; // Longer text needs more particles
// 3. What's the font size?const fontSize = 80; // Larger fonts need more particles
// 4. What's the importance?const importance = 'hero'; // 'background', 'hero', 'showcase'
// 5. What's the performance budget?const targetFPS = 60; // 30, 45, 60
// Decision functionfunction calculateOptimalParticles(device, textLength, fontSize, importance, targetFPS) {let base;
// Start with device baselineif (device === 'mobile') base = 1000;else if (device === 'tablet') base = 2000;else base = 3000;
// Adjust for text lengthif (textLength > 10) base *= 0.8; // Reduce for long textif (textLength < 5) base *= 1.2; // Increase for short text
// Adjust for font sizebase *= (fontSize / 80); // Scale proportionally
// Adjust for importanceif (importance === 'background') base *= 0.6;if (importance === 'showcase') base *= 1.4;
// Adjust for FPS targetif (targetFPS === 60) base *= 0.8;if (targetFPS === 30) base *= 1.3;
// Round to nearest 100return Math.round(base / 100) * 100;}
const optimalParticles = calculateOptimalParticles('desktop',8, // "PARTICLE"80,'hero',60);
const instance = initParticleJS('#canvas', {text: 'CALCULATED',colors: ['#695aa6'],maxParticles: optimalParticles});Quick Reference Guide
Section titled “Quick Reference Guide”// Copy-paste configurations for common scenarios
// Mobile backgroundconst mobileBackground = {maxParticles: 600,particleRadius: { xs: { base: 3, rand: 1 } },explosionRadius: { xs: 50, lg: 70 }};
// Mobile heroconst mobileHero = {maxParticles: 1200,particleRadius: { xs: { base: 2.5, rand: 1 } },explosionRadius: { xs: 70, lg: 100 }};
// Desktop backgroundconst desktopBackground = {maxParticles: 1500,particleRadius: { xs: { base: 2.5, rand: 1 } },explosionRadius: { xs: 70, lg: 100 }};
// Desktop heroconst desktopHero = {maxParticles: 2800,particleRadius: { xs: { base: 2, rand: 1 } },explosionRadius: { xs: 80, lg: 120 }};
// Desktop showcaseconst desktopShowcase = {maxParticles: 4500,particleRadius: { xs: { base: 2, rand: 0.5 } },explosionRadius: { xs: 100, lg: 150 }};
// 4K display showcaseconst fourKShowcase = {maxParticles: 6000,particleRadius: { xs: { base: 1.5, rand: 0.5 } },explosionRadius: { xs: 120, lg: 180 }};
// Use appropriate configconst isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);const isHero = document.querySelector('#canvas').classList.contains('hero');
let config;if (isMobile && isHero) {config = mobileHero;} else if (isMobile) {config = mobileBackground;} else if (isHero) {config = desktopHero;} else {config = desktopBackground;}
const instance = initParticleJS('#canvas', {text: 'OPTIMIZED',colors: ['#695aa6'],...config});Combining with Other Options
Section titled “Combining with Other Options”With Slow Browser Detection
Section titled “With Slow Browser Detection”const instance = initParticleJS('#canvas', {text: 'ADAPTIVE',colors: ['#695aa6'],maxParticles: 3000,
// Automatically reduce particles if slowrenderTimeThreshold: 16, // Reduce if frame takes >16ms
// ParticleText.js will automatically reduce particle count// if performance is poor, even if maxParticles is 3000});
// How it works:// 1. Starts with 3000 particles// 2. Monitors frame render time// 3. If frame time exceeds 16ms, reduces particle count// 4. Result: Adaptive performance optimizationWith Explosion Radius
Section titled “With Explosion Radius”// Large explosion radius needs fewer particlesconst sparse = initParticleJS('#canvas', {text: 'SPARSE',colors: ['#695aa6'],maxParticles: 1200, // Fewer particles
explosionRadius: { xs: 150, // Large explosion radius lg: 200}});// Fewer particles but strong interaction effect
// Small explosion radius needs more particlesconst dense = initParticleJS('#canvas', {text: 'DENSE',colors: ['#695aa6'],maxParticles: 4000, // More particles
explosionRadius: { xs: 60, // Small explosion radius lg: 80}});// Many particles but subtle interaction effect
// Balance pointconst balanced = initParticleJS('#canvas', {text: 'BALANCED',colors: ['#695aa6'],maxParticles: 2500,
explosionRadius: { xs: 80, lg: 120}});// Good particle density with good interactionWith Particle Radius
Section titled “With Particle Radius”// Strategy: Fewer large particlesconst fewLarge = initParticleJS('#canvas', {text: 'LARGE',colors: ['#695aa6'],maxParticles: 1000, // Low count
particleRadius: { xs: { base: 4, rand: 1 }, // Large particles lg: { base: 4, rand: 1 }}});// Fast performance, chunky aesthetic
// Strategy: Many small particlesconst manySmall = initParticleJS('#canvas', {text: 'SMALL',colors: ['#695aa6'],maxParticles: 5000, // High count
particleRadius: { xs: { base: 1.5, rand: 0.5 }, // Small particles lg: { base: 1.5, rand: 0.5 }}});// Slower performance, fine-grained aesthetic
// Recommended: Balanced approachconst balanced = initParticleJS('#canvas', {text: 'BALANCED',colors: ['#695aa6'],maxParticles: 2500,
particleRadius: { xs: { base: 2, rand: 1 }, lg: { base: 2, rand: 1 }}});With Multiple Colors
Section titled “With Multiple Colors”// More colors = more variety neededconst multiColor = initParticleJS('#canvas', {text: 'COLORFUL',colors: ['#FF6B6B', '#4ECDC4', '#FFD93D', '#95E1D3', '#F38181'],maxParticles: 3500, // Higher count for color distribution
// Optional: Weight colorscolorWeights: [2, 2, 1, 1, 1] // More red and cyan});// Need more particles to show all colors properly
// Fewer colors = fewer particles neededconst singleColor = initParticleJS('#canvas', {text: 'SIMPLE',colors: ['#695aa6'],maxParticles: 2000, // Lower count sufficient});// Single color looks good with fewer particlesWith Custom Breakpoints
Section titled “With Custom Breakpoints”const instance = initParticleJS('#canvas', {text: 'RESPONSIVE',colors: ['#695aa6'],
// Different particle counts per breakpointmaxParticles: 3000, // Desktop default
// Configure custom breakpointsxs: { maxParticles: 800, // Mobile fontSize: 60},
sm: { maxParticles: 1200, // Tablet portrait fontSize: 70},
md: { maxParticles: 2000, // Tablet landscape fontSize: 80},
lg: { maxParticles: 3000, // Desktop fontSize: 100}});
// Note: maxParticles in breakpoints is not officially supported// Better approach: Reinitialize on resizelet currentBreakpoint = getCurrentBreakpoint();
window.addEventListener('resize', () => {const newBreakpoint = getCurrentBreakpoint();
if (newBreakpoint !== currentBreakpoint) { instance.destroy();
const particleCount = { xs: 800, sm: 1200, md: 2000, lg: 3000 }[newBreakpoint];
instance = initParticleJS('#canvas', { text: 'RESPONSIVE', colors: ['#695aa6'], maxParticles: particleCount });
currentBreakpoint = newBreakpoint;}});
function getCurrentBreakpoint() {const width = window.innerWidth;if (width < 640) return 'xs';if (width < 768) return 'sm';if (width < 1024) return 'md';return 'lg';}With Manual Animation Control
Section titled “With Manual Animation Control”// Don't waste particles on hidden animationsconst instance = initParticleJS('#canvas', {text: 'EFFICIENT',colors: ['#695aa6'],maxParticles: 4000, // Can afford more since not always running
autoAnimate: false, // Don't start immediatelytrackCursorOnlyInsideCanvas: true});
// Use Intersection Observer for lazy loadingconst observer = new IntersectionObserver((entries) => {entries.forEach(entry => { if (entry.isIntersecting) { instance.startAnimation(); } else { instance.destroy(); // Free up resources }});}, {threshold: 0.2});
observer.observe(document.getElementById('canvas'));
// Result: Can use higher particle counts because animation// only runs when visibleTesting Different Values
Section titled “Testing Different Values”Interactive Testing
Section titled “Interactive Testing”// Create a testing interfaceconst canvas = document.getElementById('canvas');const slider = document.getElementById('particle-slider');const display = document.getElementById('particle-count');const fpsDisplay = document.getElementById('fps');
let instance;let lastTime = performance.now();let frameCount = 0;
function updateParticleCount(count) {// Clean up previous instanceif (instance) { instance.destroy();}
// Create new instance with new countinstance = initParticleJS('#canvas', { text: 'TEST', colors: ['#695aa6'], maxParticles: count});
display.textContent = count;}
// Slider controlslider.addEventListener('input', (e) => {const count = parseInt(e.target.value);updateParticleCount(count);});
// FPS monitoringfunction measureFPS() {frameCount++;const currentTime = performance.now();const elapsed = currentTime - lastTime;
if (elapsed >= 1000) { const fps = Math.round((frameCount * 1000) / elapsed); fpsDisplay.textContent = `${fps} FPS`;
frameCount = 0; lastTime = currentTime;}
requestAnimationFrame(measureFPS);}
measureFPS();
// HTML:// <input type="range" id="particle-slider" min="100" max="8000" step="100" value="2000">// <div>Particles: <span id="particle-count">2000</span></div>// <div>FPS: <span id="fps">--</span></div>A/B Testing
Section titled “A/B Testing”// Test two configurations side-by-sideconst canvas1 = initParticleJS('#canvas1', {text: 'CONFIG A',colors: ['#FF6B6B'],maxParticles: 1500,particleRadius: { xs: { base: 3, rand: 1 } }});
const canvas2 = initParticleJS('#canvas2', {text: 'CONFIG B',colors: ['#4ECDC4'],maxParticles: 3000,particleRadius: { xs: { base: 2, rand: 1 } }});
// Collect metricsfunction collectMetrics(instance, name) {const metrics = { name: name, particleCount: instance.particleList.length, fps: 0, frameTime: 0};
let frameCount = 0;let totalFrameTime = 0;let lastTime = performance.now();
function measure() { const startTime = performance.now();
// Measure frame time frameCount++; const currentTime = performance.now(); const frameTime = currentTime - startTime; totalFrameTime += frameTime;
const elapsed = currentTime - lastTime; if (elapsed >= 2000) { // 2 second sample metrics.fps = Math.round((frameCount * 1000) / elapsed); metrics.frameTime = (totalFrameTime / frameCount).toFixed(2);
console.log(`${metrics.name}:`, metrics);
frameCount = 0; totalFrameTime = 0; lastTime = currentTime; }
requestAnimationFrame(measure);}
measure();}
collectMetrics(canvas1, 'Config A');collectMetrics(canvas2, 'Config B');Performance Monitoring
Section titled “Performance Monitoring”Real-Time Particle Count
Section titled “Real-Time Particle Count”const instance = initParticleJS('#canvas', {text: 'MONITOR',colors: ['#695aa6'],maxParticles: 3000,renderTimeThreshold: 16 // Auto-reduce if slow});
// Monitor actual particle countsetInterval(() => {const actualCount = instance.particleList.length;const configuredMax = 3000;
console.log({ configured: configuredMax, actual: actualCount, reduced: actualCount < configuredMax, percentage: ((actualCount / configuredMax) * 100).toFixed(1) + '%'});
// If actual < configured, slow browser detection kicked inif (actualCount < configuredMax) { console.warn('Particle count reduced due to performance');}}, 1000);Performance Analytics
Section titled “Performance Analytics”// Collect performance data for analyticsfunction trackParticlePerformance(instance, config) {const metrics = { maxParticles: config.maxParticles, actualParticles: instance.particleList.length, deviceType: /iPhone|iPad|iPod|Android/i.test(navigator.userAgent) ? 'mobile' : 'desktop', screenWidth: window.innerWidth, userAgent: navigator.userAgent, samples: []};
let sampleCount = 0;const maxSamples = 60; // Collect 60 samples (1 second at 60 FPS)let startTime = performance.now();
function sample() { const currentTime = performance.now(); const frameTime = currentTime - startTime;
metrics.samples.push(frameTime); sampleCount++;
if (sampleCount >= maxSamples) { // Calculate statistics const avgFrameTime = metrics.samples.reduce((a, b) => a + b, 0) / metrics.samples.length; const maxFrameTime = Math.max(...metrics.samples); const minFrameTime = Math.min(...metrics.samples); const avgFPS = 1000 / avgFrameTime;
const report = { ...metrics, avgFrameTime: avgFrameTime.toFixed(2), maxFrameTime: maxFrameTime.toFixed(2), minFrameTime: minFrameTime.toFixed(2), avgFPS: avgFPS.toFixed(1), recommendation: getRecommendation(avgFPS, metrics.maxParticles) };
// Send to analytics console.log('Performance Report:', report); // analytics.track('particle_performance', report);
return; }
startTime = currentTime; requestAnimationFrame(sample);}
requestAnimationFrame(sample);}
function getRecommendation(fps, maxParticles) {if (fps >= 55) { return `Excellent performance. Could increase to ${Math.round(maxParticles * 1.3)} particles.`;} else if (fps >= 45) { return 'Good performance. Current settings optimal.';} else if (fps >= 30) { return `Fair performance. Consider reducing to ${Math.round(maxParticles * 0.7)} particles.`;} else { return `Poor performance. Reduce to ${Math.round(maxParticles * 0.5)} particles.`;}}
// Use itconst instance = initParticleJS('#canvas', {text: 'ANALYTICS',colors: ['#695aa6'],maxParticles: 3000});
trackParticlePerformance(instance, { maxParticles: 3000 });Best Practices
Section titled “Best Practices”1. Start Conservative
Section titled “1. Start Conservative”// ✅ Good: Start low, increase if neededlet instance = initParticleJS('#canvas', {text: 'START LOW',colors: ['#695aa6'],maxParticles: 1500 // Conservative starting point});
// Test performance, then increase if smoothsetTimeout(() => {const fps = measureCurrentFPS();if (fps > 55) { console.log('Performance good, can increase particles'); instance.destroy(); instance = initParticleJS('#canvas', { text: 'INCREASED', colors: ['#695aa6'], maxParticles: 2500 });}}, 2000);
// ❌ Bad: Start with maximum, users experience lag immediatelyconst instance = initParticleJS('#canvas', {text: 'TOO HIGH',colors: ['#695aa6'],maxParticles: 8000 // Likely to cause lag});2. Test on Real Devices
Section titled “2. Test on Real Devices”// ✅ Good: Test on actual target devices// Test checklist:// - iPhone SE (low-end mobile)// - iPhone 12+ (modern mobile)// - iPad (tablet)// - MacBook Pro (laptop)// - Desktop (high-end)// - 4K display (ultra high-end)
const testConfigs = [{ device: 'iPhone SE', maxParticles: 800 },{ device: 'iPhone 12', maxParticles: 1500 },{ device: 'iPad', maxParticles: 2000 },{ device: 'Laptop', maxParticles: 2800 },{ device: 'Desktop', maxParticles: 4000 }];
// Record actual FPS on each device// Adjust maxParticles based on results
// ❌ Bad: Only test on your development machineconst instance = initParticleJS('#canvas', {text: 'UNTESTED',colors: ['#695aa6'],maxParticles: 5000 // Works on M1 MacBook Pro, might fail on older devices});3. Provide User Control
Section titled “3. Provide User Control”// ✅ Good: Let users adjust if neededconst qualitySettings = {low: 1000,medium: 2500,high: 5000};
// Load from localStorage or default to 'medium'let quality = localStorage.getItem('particleQuality') || 'medium';
let instance = initParticleJS('#canvas', {text: 'QUALITY',colors: ['#695aa6'],maxParticles: qualitySettings[quality]});
// Provide UI controldocument.getElementById('quality-select').addEventListener('change', (e) => {quality = e.target.value;localStorage.setItem('particleQuality', quality);
instance.destroy();instance = initParticleJS('#canvas', { text: 'QUALITY', colors: ['#695aa6'], maxParticles: qualitySettings[quality]});});
// HTML:// <select id="quality-select">// <option value="low">Low (Better Performance)</option>// <option value="medium" selected>Medium (Balanced)</option>// <option value="high">High (Better Quality)</option>// </select>
// ❌ Bad: Force one setting for all usersconst instance = initParticleJS('#canvas', {text: 'FIXED',colors: ['#695aa6'],maxParticles: 5000 // No user control});4. Use with Slow Browser Detection
Section titled “4. Use with Slow Browser Detection”// ✅ Good: Combine with automatic adaptationconst instance = initParticleJS('#canvas', {text: 'ADAPTIVE',colors: ['#695aa6'],maxParticles: 3000, // Starting point
// Enable automatic reductionrenderTimeThreshold: 16, // Reduce if frame > 16ms (60 FPS)});
// Library will automatically reduce particle count if needed// You set the maximum, library ensures good performance
// ❌ Bad: High fixed count without fallbackconst instance = initParticleJS('#canvas', {text: 'RISKY',colors: ['#695aa6'],maxParticles: 5000,renderTimeThreshold: 9999 // Effectively disabled});// Will lag on slower devices with no adaptation5. Document Your Choices
Section titled “5. Document Your Choices”// ✅ Good: Clear documentationconst instance = initParticleJS('#canvas', {text: 'HERO',colors: ['#695aa6'],
// Set to 2500 after testing on:// - iPhone 12: 45-50 FPS// - MacBook Pro M1: 60 FPS// - Desktop (RTX 3080): 60 FPS// Trade-off: Slightly lower quality on mobile, but smooth animationmaxParticles: 2500});
// ❌ Bad: Magic numbers without contextconst instance = initParticleJS('#canvas', {text: 'MYSTERY',colors: ['#695aa6'],maxParticles: 2137 // Why this specific number?});Troubleshooting
Section titled “Troubleshooting”Particle Count Lower Than Expected?
Section titled “Particle Count Lower Than Expected?”const instance = initParticleJS('#canvas', {text: 'TEST',colors: ['#695aa6'],maxParticles: 5000});
console.log('Expected:', 5000);console.log('Actual:', instance.particleList.length);
// If actual < expected, possible reasons:
// 1. Slow browser detection reduced count// Check: renderTimeThreshold settingif (instance.particleList.length < 5000) {console.log('Slow browser detection may have reduced particles');// Solution: Increase renderTimeThreshold or disableinstance.destroy();instance = initParticleJS('#canvas', { text: 'TEST', colors: ['#695aa6'], maxParticles: 5000, renderTimeThreshold: 9999 // Effectively disable});}
// 2. Text is simple/small (not enough pixels to generate particles)// Check: Font size and text lengthconsole.log('Text length:', instance.text.length);// Solution: Increase fontSize or use longer text
// 3. Particle sampling skipped some pixels// This is normal - not every pixel becomes a particle// Solution: Increase font size or reduce particle radiusPerformance Still Poor?
Section titled “Performance Still Poor?”// If setting low maxParticles doesn't help:
// 1. Check other factorsconst instance = initParticleJS('#canvas', {text: 'SLOW',colors: ['#695aa6'],maxParticles: 1000, // Already low
// Other performance factors:explosionRadius: { xs: 50, lg: 70 }, // Reduce explosion radiusparticleRadius: { xs: { base: 3, rand: 1 } }, // Larger particles (less rendering)particleFriction: 0.98, // Faster settlingtrackCursorOnlyInsideCanvas: true // Reduce calculations});
// 2. Use manual animation controlinstance.destroy();const betterInstance = initParticleJS('#canvas', {text: 'OPTIMIZED',colors: ['#695aa6'],maxParticles: 1000,autoAnimate: false});
// Only animate when visibleconst observer = new IntersectionObserver((entries) => {entries.forEach(entry => { if (entry.isIntersecting) { betterInstance.startAnimation(); } else { betterInstance.destroy(); }});});
observer.observe(document.getElementById('canvas'));
// 3. Throttle frame ratelet lastFrame = 0;const fps30 = 1000 / 30;
function throttledLoop(currentTime) {if (currentTime - lastFrame >= fps30) { betterInstance.forceRequestAnimationFrame(); lastFrame = currentTime;}requestAnimationFrame(throttledLoop);}Visual Quality Not Good Enough?
Section titled “Visual Quality Not Good Enough?”// If increasing maxParticles causes lag:
// Solution 1: Compensate with larger particlesconst instance = initParticleJS('#canvas', {text: 'QUALITY',colors: ['#695aa6'],maxParticles: 1500, // Keep low for performance
particleRadius: { xs: { base: 3.5, rand: 1 }, // Larger particles fill gaps lg: { base: 3.5, rand: 1 }}});
// Solution 2: Increase font sizeconst instance2 = initParticleJS('#canvas', {text: 'BIG',colors: ['#695aa6'],maxParticles: 1500,fontSize: 120 // Larger text = better coverage with fewer particles});
// Solution 3: Use shorter textconst instance3 = initParticleJS('#canvas', {text: 'HI', // 2 chars instead of 8colors: ['#695aa6'],maxParticles: 1500 // Same budget, better density per letter});
// Solution 4: Target only desktop usersconst isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
if (isMobile) {// Show static image or simpler effectdocument.getElementById('canvas').style.display = 'none';document.getElementById('fallback-image').style.display = 'block';} else {// Full quality on desktopconst instance = initParticleJS('#canvas', { text: 'DESKTOP', colors: ['#695aa6'], maxParticles: 5000});}Particle Count Keeps Changing?
Section titled “Particle Count Keeps Changing?”// If particleList.length varies over time:
const instance = initParticleJS('#canvas', {text: 'DYNAMIC',colors: ['#695aa6'],maxParticles: 3000,renderTimeThreshold: 16 // Slow browser detection enabled});
// Monitor changeslet lastCount = instance.particleList.length;
setInterval(() => {const currentCount = instance.particleList.length;
if (currentCount !== lastCount) { console.log(`Particle count changed: ${lastCount} → ${currentCount}`); console.log('Reason: Slow browser detection adjusted count'); lastCount = currentCount;}}, 1000);
// This is expected behavior when renderTimeThreshold is set// To prevent changes, set very high threshold:instance.destroy();const stable = initParticleJS('#canvas', {text: 'STABLE',colors: ['#695aa6'],maxParticles: 3000,renderTimeThreshold: 9999 // Won't auto-adjust});
// Note: Particle count can only decrease, never increase// Once reduced, it stays reducedRelated Documentation
Section titled “Related Documentation”- maxParticles (API Reference) - Full API documentation
- Performance Optimization Guide - Comprehensive performance guide
- Slow Browser Detection - Automatic performance adaptation
- Particle Radius - Adjust particle size
- Explosion Radius - Control interaction distance
- Manual Animation Control - Start/stop animation programmatically
- Restricted Cursor Tracking - Limit cursor tracking for performance