optimizations for mobile

This commit is contained in:
2025-12-29 16:49:02 +02:00
parent d078706c50
commit a28d09915c
3 changed files with 85 additions and 12 deletions

View File

@@ -11,7 +11,11 @@
}>(); }>();
let particles: Particle[] = []; let particles: Particle[] = [];
const numParticles = 40; // Reduced from 60 for better performance with two visualizations // Detect mobile for performance optimization
const isMobile = typeof window !== 'undefined' &&
(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
window.innerWidth < 768);
const numParticles = isMobile ? 15 : 40; // Significantly reduced for mobile
let audioData: Float32Array | null = null; let audioData: Float32Array | null = null;
// Cache frequently used calculations // Cache frequently used calculations
@@ -19,6 +23,10 @@
let midRange = 0; let midRange = 0;
let trebleStart = 0; let trebleStart = 0;
// Track if user is scrolling to reduce load
let isScrolling = false;
let scrollTimeout: number;
class Particle { class Particle {
x: number; x: number;
y: number; y: number;
@@ -42,13 +50,14 @@
this.alpha = p.random(100, 255); this.alpha = p.random(100, 255);
} }
update(p: p5, audioLevel: number, bass: number, mid: number) { update(p: p5, audioLevel: number, bass: number, mid: number, isMobile: boolean) {
// Base movement - gentle constant speed // Base movement - gentle constant speed
this.x += this.vx; this.x += this.vx;
this.y += this.vy; this.y += this.vy;
// Subtle audio reactive displacement (only every 3rd frame for performance) // Subtle audio reactive displacement (less frequent on mobile)
if (p.frameCount % 3 === 0) { const updateFrequency = isMobile ? 5 : 3;
if (p.frameCount % updateFrequency === 0) {
const displacement = audioLevel * 20; const displacement = audioLevel * 20;
const angle = p.noise(this.x * 0.01, this.y * 0.01, p.frameCount * 0.01) * p.TWO_PI; const angle = p.noise(this.x * 0.01, this.y * 0.01, p.frameCount * 0.01) * p.TWO_PI;
this.x += p.cos(angle) * displacement * 0.05; this.x += p.cos(angle) * displacement * 0.05;
@@ -86,7 +95,8 @@
const sketch = (p: p5) => { const sketch = (p: p5) => {
p.setup = () => { p.setup = () => {
p.createCanvas(width, height); p.createCanvas(width, height);
p.frameRate(30); // Reduce from 60fps to 30fps for better performance // Lower framerate on mobile for better performance
p.frameRate(isMobile ? 20 : 30);
p.background(0); p.background(0);
// Initialize particles // Initialize particles
@@ -94,11 +104,22 @@
for (let i = 0; i < numParticles; i++) { for (let i = 0; i < numParticles; i++) {
particles.push(new Particle(p, i)); particles.push(new Particle(p, i));
} }
// Handle scroll events to pause visualization
if (typeof window !== 'undefined') {
window.addEventListener('scroll', () => {
isScrolling = true;
clearTimeout(scrollTimeout);
scrollTimeout = window.setTimeout(() => {
isScrolling = false;
}, 150);
}, { passive: true });
}
}; };
p.draw = () => { p.draw = () => {
// Pause visualization when document is hidden (tab not focused) // Pause visualization when document is hidden, scrolling, or on mobile during heavy load
if (document.hidden) { if (document.hidden || (isMobile && isScrolling)) {
return; return;
} }
p.background(0, 30); // Fade effect p.background(0, 30); // Fade effect
@@ -153,12 +174,12 @@
// Update and display particles // Update and display particles
for (let i = 0; i < particles.length; i++) { for (let i = 0; i < particles.length; i++) {
particles[i].update(p, audioLevel, bass, mid); particles[i].update(p, audioLevel, bass, mid, isMobile);
particles[i].display(p, audioLevel); particles[i].display(p, audioLevel);
} }
// Draw connections less frequently (every other frame) for better performance // Draw connections - skip on mobile for better performance
if (p.frameCount % 2 === 0) { if (!isMobile && p.frameCount % 2 === 0) {
for (let i = 0; i < particles.length; i++) { for (let i = 0; i < particles.length; i++) {
// Only check next 5 particles instead of all, reduces O(n²) significantly // Only check next 5 particles instead of all, reduces O(n²) significantly
for (let j = i + 1; j < Math.min(i + 6, particles.length); j++) { for (let j = i + 1; j < Math.min(i + 6, particles.length); j++) {

View File

@@ -19,6 +19,12 @@
let isPlaying = $state(false); let isPlaying = $state(false);
let isInitialized = $state(false); let isInitialized = $state(false);
// Detect mobile for visualization sizing
const isMobile = typeof window !== 'undefined' &&
(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
window.innerWidth < 768);
const vizSize = isMobile ? 280 : 400;
// Audio components // Audio components
let noiseSynth: Tone.NoiseSynth | null = null; let noiseSynth: Tone.NoiseSynth | null = null;
let reverb: Tone.Reverb | null = null; let reverb: Tone.Reverb | null = null;
@@ -192,6 +198,26 @@
// Lifecycle // Lifecycle
onMount(() => { onMount(() => {
initializeAudio(); initializeAudio();
// Handle page visibility changes (screen sleep/wake, tab switching)
const handleVisibilityChange = async () => {
if (!document.hidden && isPlaying) {
// Page is visible again - resume audio context if suspended
if (Tone.getContext().state === 'suspended') {
await Tone.getContext().resume();
}
// Restart loop if it stopped
if (loop && loop.state !== 'started') {
loop.start(0);
}
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}); });
onDestroy(() => { onDestroy(() => {
@@ -243,6 +269,6 @@
</div> </div>
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<AudioVisualization {isPlaying} {analyser} width={400} height={400} /> <AudioVisualization {isPlaying} {analyser} width={vizSize} height={vizSize} />
</div> </div>
</div> </div>

View File

@@ -31,6 +31,12 @@
let isPlaying = $state(false); let isPlaying = $state(false);
let isInitialized = $state(false); let isInitialized = $state(false);
// Detect mobile for visualization sizing
const isMobile = typeof window !== 'undefined' &&
(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent) ||
window.innerWidth < 768);
const vizSize = isMobile ? 280 : 400;
// Audio components // Audio components
let synth: Tone.PolySynth | null = null; let synth: Tone.PolySynth | null = null;
let arpSynth: Tone.Synth | null = null; let arpSynth: Tone.Synth | null = null;
@@ -397,6 +403,26 @@
// Lifecycle // Lifecycle
onMount(() => { onMount(() => {
initializeAudio(); initializeAudio();
// Handle page visibility changes (screen sleep/wake, tab switching)
const handleVisibilityChange = async () => {
if (!document.hidden && isPlaying) {
// Page is visible again - resume audio context if suspended
if (Tone.getContext().state === 'suspended') {
await Tone.getContext().resume();
}
// Restart transport if it stopped
if (Tone.getTransport().state !== 'started') {
Tone.getTransport().start();
}
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}); });
onDestroy(() => { onDestroy(() => {
@@ -471,6 +497,6 @@
</div> </div>
<div class="flex items-center justify-center"> <div class="flex items-center justify-center">
<AudioVisualization {isPlaying} {analyser} width={400} height={400} /> <AudioVisualization {isPlaying} {analyser} width={vizSize} height={vizSize} />
</div> </div>
</div> </div>