diff --git a/components/about/scroll-logos.tsx b/components/about/scroll-logos.tsx index fd580b6..99df6ef 100644 --- a/components/about/scroll-logos.tsx +++ b/components/about/scroll-logos.tsx @@ -9,27 +9,43 @@ function ScrollLogos() { const scrollContainer = scrollRef.current; if (!scrollContainer) return; - let currentScrollPosition = 0; - const startScrolling = () => { - const move = () => { + const mediaQuery = typeof window !== 'undefined' && 'matchMedia' in window ? window.matchMedia('(prefers-reduced-motion: reduce)') : null; + if (mediaQuery?.matches) { + scrollContainer.scrollTo(0, 0); + return; + } + + let currentScrollPosition = scrollContainer.scrollLeft || 0; + let animationFrameId: number | null = null; + let lastTimestamp = performance.now(); + const frameDuration = 30; + + const step = (timestamp: number) => { + const elapsed = timestamp - lastTimestamp; + + if (elapsed >= frameDuration) { const totalWidth = scrollContainer.scrollWidth; - currentScrollPosition += 1; // move 1px per interval + currentScrollPosition += 1; if (currentScrollPosition >= totalWidth / 2) { - currentScrollPosition = 0; // reset to 0 when it reaches the end + currentScrollPosition = 0; } scrollContainer.scrollTo(currentScrollPosition, 0); - }; + lastTimestamp = timestamp; + } - return setInterval(move, 16); // move every 20ms + animationFrameId = requestAnimationFrame(step); }; - const intervalId = startScrolling(); + animationFrameId = requestAnimationFrame(step); - // clear interval on unmount - return () => clearInterval(intervalId); + return () => { + if (animationFrameId !== null) { + cancelAnimationFrame(animationFrameId); + } + }; }, []); const logos = useMemo( diff --git a/components/shared/scroll-icons.tsx b/components/shared/scroll-icons.tsx index d910b66..ef7499e 100644 --- a/components/shared/scroll-icons.tsx +++ b/components/shared/scroll-icons.tsx @@ -14,28 +14,44 @@ function ScrollIcons() { const scrollContainer = scrollRef.current; if (!scrollContainer) return; - let currentScrollPosition = 0; - const startScrolling = () => { - const move = () => { + const mediaQuery = typeof window !== 'undefined' && 'matchMedia' in window ? window.matchMedia('(prefers-reduced-motion: reduce)') : null; + if (mediaQuery?.matches) { + scrollContainer.scrollTo(0, 0); + return; + } + + let currentScrollPosition = scrollContainer.scrollLeft || 0; + let animationFrameId: number | null = null; + let lastTimestamp = performance.now(); + const frameDuration = isMobile ? 48 : 30; // throttle updates to ~20-30 FPS + + const step = (timestamp: number) => { + const elapsed = timestamp - lastTimestamp; + + if (elapsed >= frameDuration) { const totalWidth = scrollContainer.scrollWidth; - currentScrollPosition += 1; // move 1px per interval + currentScrollPosition += 1; if (currentScrollPosition >= totalWidth / 2) { - currentScrollPosition = 0; // reset to 0 when it reaches the end + currentScrollPosition = 0; } scrollContainer.scrollTo(currentScrollPosition, 0); - }; + lastTimestamp = timestamp; + } - return setInterval(move, isMobile ? 48 : 20); // faster scrolling on desktop + animationFrameId = requestAnimationFrame(step); }; - const intervalId = startScrolling(); + animationFrameId = requestAnimationFrame(step); - // clear interval on unmount - return () => clearInterval(intervalId); - }, []); + return () => { + if (animationFrameId !== null) { + cancelAnimationFrame(animationFrameId); + } + }; + }, [isMobile]); const logos = useMemo( () => [ ...aboutPageConfig.developers.logos,