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[] = [];
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;
// Cache frequently used calculations
@@ -19,6 +23,10 @@
let midRange = 0;
let trebleStart = 0;
// Track if user is scrolling to reduce load
let isScrolling = false;
let scrollTimeout: number;
class Particle {
x: number;
y: number;
@@ -42,13 +50,14 @@
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
this.x += this.vx;
this.y += this.vy;
// Subtle audio reactive displacement (only every 3rd frame for performance)
if (p.frameCount % 3 === 0) {
// Subtle audio reactive displacement (less frequent on mobile)
const updateFrequency = isMobile ? 5 : 3;
if (p.frameCount % updateFrequency === 0) {
const displacement = audioLevel * 20;
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;
@@ -86,7 +95,8 @@
const sketch = (p: p5) => {
p.setup = () => {
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);
// Initialize particles
@@ -94,11 +104,22 @@
for (let i = 0; i < numParticles; 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 = () => {
// Pause visualization when document is hidden (tab not focused)
if (document.hidden) {
// Pause visualization when document is hidden, scrolling, or on mobile during heavy load
if (document.hidden || (isMobile && isScrolling)) {
return;
}
p.background(0, 30); // Fade effect
@@ -153,12 +174,12 @@
// Update and display particles
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);
}
// Draw connections less frequently (every other frame) for better performance
if (p.frameCount % 2 === 0) {
// Draw connections - skip on mobile for better performance
if (!isMobile && p.frameCount % 2 === 0) {
for (let i = 0; i < particles.length; i++) {
// 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++) {

View File

@@ -19,6 +19,12 @@
let isPlaying = $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
let noiseSynth: Tone.NoiseSynth | null = null;
let reverb: Tone.Reverb | null = null;
@@ -192,6 +198,26 @@
// Lifecycle
onMount(() => {
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(() => {
@@ -243,6 +269,6 @@
</div>
<div class="flex items-center justify-center">
<AudioVisualization {isPlaying} {analyser} width={400} height={400} />
<AudioVisualization {isPlaying} {analyser} width={vizSize} height={vizSize} />
</div>
</div>

View File

@@ -31,6 +31,12 @@
let isPlaying = $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
let synth: Tone.PolySynth | null = null;
let arpSynth: Tone.Synth | null = null;
@@ -397,6 +403,26 @@
// Lifecycle
onMount(() => {
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(() => {
@@ -471,6 +497,6 @@
</div>
<div class="flex items-center justify-center">
<AudioVisualization {isPlaying} {analyser} width={400} height={400} />
<AudioVisualization {isPlaying} {analyser} width={vizSize} height={vizSize} />
</div>
</div>