From 0dbd277ed6a9bf6719346b25e3c838c185a62e60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Mar 2026 09:03:54 +0000 Subject: [PATCH] fix: prevent mobile/iPad crash from memory leaks and excessive rendering Co-authored-by: Apurv-15 <95355502+Apurv-15@users.noreply.github.com> Agent-Logs-Url: https://github.com/GDGVITM/Spectrum-web-2026/sessions/29040fd8-30cf-4d5f-aa10-012ec8a99884 --- package-lock.json | 4 +-- src/pages/events/Events.tsx | 6 +++-- src/pages/invasion/Invasion.tsx | 31 +++++++++++++---------- src/pages/landingRevamp/LandingRevamp.tsx | 21 +++++++++++---- src/utils/debounce.ts | 9 ++++++- 5 files changed, 48 insertions(+), 23 deletions(-) diff --git a/package-lock.json b/package-lock.json index 059a808..56a14f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1758,7 +1758,7 @@ "version": "19.1.8", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.8.tgz", "integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -2420,7 +2420,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/debug": { diff --git a/src/pages/events/Events.tsx b/src/pages/events/Events.tsx index cf5bd48..effca8f 100644 --- a/src/pages/events/Events.tsx +++ b/src/pages/events/Events.tsx @@ -6,6 +6,7 @@ import styles from "./Events.module.scss"; import BackButton from "../components/backButton/BackButton"; import { navContext } from "../../App"; import EventHouse from "../components/eventHouse/EventHouse"; +import { isTouchDevice } from "../../utils/debounce"; gsap.registerPlugin(ScrollTrigger); @@ -86,6 +87,7 @@ export default function Events() { const timelineRef = useRef(null); const progressRef = useRef(null); const cardRefs = useRef<(HTMLDivElement | null)[]>([]); + const isMobile = isTouchDevice(); useGSAP(() => { if (!bgRef.current || !containerRef.current) return; @@ -152,7 +154,7 @@ export default function Events() { {/* Environmental Animation Layer - Sakura & Lanterns */}
- {[...Array(25)].map((_, i) => ( + {[...Array(isMobile ? 10 : 25)].map((_, i) => (
))} - {[...Array(6)].map((_, i) => ( + {[...Array(isMobile ? 2 : 6)].map((_, i) => (
{ const containerRef = useRef(null); @@ -22,6 +23,8 @@ const SamuraiBackground = () => { }, }); + const isMobile = isTouchDevice(); + // Function to create drifting particles (petals, embers, ash) const createParticles = (className: string, count: number, direction: 'up' | 'down') => { for (let i = 0; i < count; i++) { @@ -72,21 +75,23 @@ const SamuraiBackground = () => { }; // Create different particle types - createParticles(styles.petal, 20, 'down'); // Falling petals - createParticles(styles.ember, 40, 'up'); // More rising embers - createParticles(styles.ash, 60, 'up'); // Doubled ash count + createParticles(styles.petal, isMobile ? 8 : 20, 'down'); // Falling petals + createParticles(styles.ember, isMobile ? 15 : 40, 'up'); // More rising embers + createParticles(styles.ash, isMobile ? 20 : 60, 'up'); // Doubled ash count - // Add a flickering effect specifically for embers - const embers = particlesRef.current?.querySelectorAll(`.${styles.ember}`); - embers?.forEach(ember => { - gsap.to(ember, { - opacity: 0.4, - repeat: -1, - yoyo: true, - duration: 0.2 + Math.random() * 0.3, - ease: "sine.inOut" + // Add a flickering effect specifically for embers (skip on mobile to save CPU) + if (!isMobile) { + const embers = particlesRef.current?.querySelectorAll(`.${styles.ember}`); + embers?.forEach(ember => { + gsap.to(ember, { + opacity: 0.4, + repeat: -1, + yoyo: true, + duration: 0.2 + Math.random() * 0.3, + ease: "sine.inOut" + }); }); - }); + } }, { scope: containerRef }); diff --git a/src/pages/landingRevamp/LandingRevamp.tsx b/src/pages/landingRevamp/LandingRevamp.tsx index afeb0e4..e16fcf8 100644 --- a/src/pages/landingRevamp/LandingRevamp.tsx +++ b/src/pages/landingRevamp/LandingRevamp.tsx @@ -5,6 +5,7 @@ import useOverlayStore from "../../utils/store"; import Lenis from "lenis"; import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; +import { isTouchDevice } from "../../utils/debounce"; const Navbar = lazy(() => import("../components/navbar/Navbar")); const MainHam = lazy(() => import("../components/mainHam/mainHam")); @@ -289,12 +290,13 @@ export default function LandingRevamp({ return () => cancelAnimationFrame(rafIdRef.current); }, []); - // ─── Canvas resize (DPR capped at 2) ────────────────────────────────────── + // ─── Canvas resize (DPR capped at 1 on mobile, 2 on desktop) ───────────── useEffect(() => { const handleResize = () => { const canvas = canvasRef.current; if (!canvas) return; - const dpr = Math.min(window.devicePixelRatio || 1, 2); + const isMobile = window.innerWidth < 768; + const dpr = isMobile ? 1 : Math.min(window.devicePixelRatio || 1, 2); canvas.width = window.innerWidth * dpr; canvas.height = window.innerHeight * dpr; drawFrame(Math.round(currentFrameRef.current)); @@ -306,21 +308,30 @@ export default function LandingRevamp({ // ─── Lenis smooth scroll ────────────────────────────────────────────────── useEffect(() => { + // Disable Lenis on touch/mobile devices — native scroll is sufficient + // and smooth scroll adds significant CPU overhead that can crash mobile browsers. + if (isTouchDevice()) return; + lenisRef.current = new Lenis({ duration: 1.8, easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)), smoothWheel: true, wheelMultiplier: 1.5, }); + + let lenisRafId: number; const raf = (time: number) => { lenisRef.current?.raf(time); - requestAnimationFrame(raf); + lenisRafId = requestAnimationFrame(raf); }; - requestAnimationFrame(raf); + lenisRafId = requestAnimationFrame(raf); lenisRef.current.on("scroll", ScrollTrigger.update); - return () => lenisRef.current?.destroy(); + return () => { + cancelAnimationFrame(lenisRafId); + lenisRef.current?.destroy(); + }; }, []); // ─── Scroll listener ───────────────────────────────────────────────────── diff --git a/src/utils/debounce.ts b/src/utils/debounce.ts index fbc3cd1..a160e95 100644 --- a/src/utils/debounce.ts +++ b/src/utils/debounce.ts @@ -8,4 +8,11 @@ const debouncedHandler = (callback: () => void, period: number) => { }; } -export default debouncedHandler; \ No newline at end of file +export default debouncedHandler; + +/** + * Returns true when the device is a touch-primary device (phone/tablet). + * Evaluated once per call — callers should cache the result where needed. + */ +export const isTouchDevice = (): boolean => + window.matchMedia("(hover: none) and (pointer: coarse)").matches;