optimizations for mobile
This commit is contained in:
@@ -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++) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user