diff --git a/src/index.js b/src/index.js index f7e5fd8..5789514 100644 --- a/src/index.js +++ b/src/index.js @@ -239,7 +239,8 @@ const AnimatedBackground = React.memo(({ useEffect(() => { const animation = setupCanvas(); let lastTime = 0; - const frameInterval = 1000 / fps; + let lastEvalTime = 0; + let currentFps = fps; const loop = (currentTime) => { // Check animation controls @@ -250,6 +251,28 @@ const AnimatedBackground = React.memo(({ animationRef.current = requestAnimationFrame(loop); + // Adaptive performance adjustments + if (adaptivePerformance && performanceMonitor) { + const { avgFps, performanceLevel } = performanceMonitor; + + // Only evaluate adaptive adjustments once per second to prevent rapid toggling + // and console spamming + if (currentTime - lastEvalTime > 1000) { + lastEvalTime = currentTime; + if (performanceLevel === 'poor' && avgFps > 0 && avgFps < 25) { + if (currentFps > 15) { + // Automatically reduce fps to improve rendering stability + currentFps = Math.max(15, currentFps - 5); + console.warn(`Poor performance detected. Automatically reduced target FPS to ${currentFps}.`); + } + } else if (performanceLevel === 'excellent' && avgFps > 55 && currentFps < fps) { + // Gradually restore fps if performance is excellent + currentFps = Math.min(fps, currentFps + 1); + } + } + } + + const frameInterval = 1000 / currentFps; const deltaTime = currentTime - lastTime; const speedMultiplier = animationControls ? animationControls.speed : 1; const adjustedFrameInterval = frameInterval / speedMultiplier; @@ -263,15 +286,6 @@ const AnimatedBackground = React.memo(({ } animation(); - - // Adaptive performance adjustments - if (adaptivePerformance && performanceMonitor) { - const { avgFps, performanceLevel } = performanceMonitor; - if (performanceLevel === 'poor' && avgFps < 20) { - // Automatically reduce complexity or fps - console.warn('Poor performance detected. Consider reducing animation complexity.'); - } - } } }; @@ -282,7 +296,7 @@ const AnimatedBackground = React.memo(({ cancelAnimationFrame(animationRef.current); } }; - }, [setupCanvas, fps, animationControls]); // Removed changing dependencies + }, [setupCanvas, fps, animationControls, adaptivePerformance]); // Added adaptivePerformance as dependency // Resize handling effect - separate from animation useEffect(() => { diff --git a/src/utils/interactionUtils.js b/src/utils/interactionUtils.js index c2dd8c9..659c6e9 100644 --- a/src/utils/interactionUtils.js +++ b/src/utils/interactionUtils.js @@ -30,7 +30,8 @@ export const createInteractionHandler = (canvas, config = {}) => { effect = 'attract', strength = 0.5, radius = 100, - continuous = false + continuous = false, + multiTouch = false } = config; let isInteracting = false; @@ -151,7 +152,9 @@ export const createInteractionHandler = (canvas, config = {}) => { const handleTouchStart = (event) => { event.preventDefault(); - Array.from(event.touches).forEach((touch, index) => { + const touches = multiTouch ? Array.from(event.touches) : [event.touches[0]]; + touches.forEach((touch) => { + if (!touch) return; const point = { x: (touch.clientX - canvas.getBoundingClientRect().left) * (canvas.width / canvas.getBoundingClientRect().width), y: (touch.clientY - canvas.getBoundingClientRect().top) * (canvas.height / canvas.getBoundingClientRect().height), @@ -165,7 +168,9 @@ export const createInteractionHandler = (canvas, config = {}) => { const handleTouchMove = (event) => { event.preventDefault(); - Array.from(event.touches).forEach((touch) => { + const touches = multiTouch ? Array.from(event.touches) : [event.touches[0]]; + touches.forEach((touch) => { + if (!touch) return; const point = { x: (touch.clientX - canvas.getBoundingClientRect().left) * (canvas.width / canvas.getBoundingClientRect().width), y: (touch.clientY - canvas.getBoundingClientRect().top) * (canvas.height / canvas.getBoundingClientRect().height), @@ -179,9 +184,19 @@ export const createInteractionHandler = (canvas, config = {}) => { const handleTouchEnd = (event) => { event.preventDefault(); - Array.from(event.changedTouches).forEach((touch) => { + const changedTouches = Array.from(event.changedTouches); + changedTouches.forEach((touch) => { touchPoints.delete(touch.identifier); }); + + // In single-touch mode, if the primary touch is lifted but other fingers + // are still on the screen, we need to ensure the interaction points are cleared + // to prevent the effect from getting stuck, but only if there are no actual touches left + // from the event.touches. + if (!multiTouch && event.touches.length === 0) { + touchPoints.clear(); + } + interactionPoints = Array.from(touchPoints.values()); };