Skip to content

fix: prevent mobile/iPad crash — Lenis RAF leak, canvas DPR, particle overload#2

Merged
Apurv-15 merged 1 commit intomainfrom
copilot/fix-auto-loader-on-mobile
Mar 26, 2026
Merged

fix: prevent mobile/iPad crash — Lenis RAF leak, canvas DPR, particle overload#2
Apurv-15 merged 1 commit intomainfrom
copilot/fix-auto-loader-on-mobile

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 25, 2026

Website was auto-loading then freezing ("page not responding") on mobile/iPad due to three compounding issues: an infinite RAF loop that was never cancelled on unmount, oversized canvas buffers, and too many simultaneously animated DOM nodes.

Changes

🔴 Lenis RAF loop never cancelled (critical memory leak)

  • RAF ID was not stored, so cancelAnimationFrame was never called on unmount — every landing page visit stacked a new infinite loop
  • Fixed by storing lenisRafId and cancelling in cleanup
  • Disabled Lenis entirely on touch devices (isTouchDevice()) — native scroll is sufficient and Lenis adds measurable CPU overhead on mobile
// Before — leaks on every mount
const raf = (time: number) => {
  lenisRef.current?.raf(time);
  requestAnimationFrame(raf); // ID never saved
};
requestAnimationFrame(raf);
return () => lenisRef.current?.destroy(); // RAF loop still running

// After
let lenisRafId: number;
const raf = (time: number) => {
  lenisRef.current?.raf(time);
  lenisRafId = requestAnimationFrame(raf);
};
lenisRafId = requestAnimationFrame(raf);
return () => { cancelAnimationFrame(lenisRafId); lenisRef.current?.destroy(); };

🟠 Canvas DPR capped at 1 on mobile

  • Was uniformly capped at min(DPR, 2) — still doubles canvas resolution on phones, wasting GPU memory
  • Now caps at 1 when window.innerWidth < 768

🟡 Particle counts reduced on mobile

  • Events page: 25 sakura + 6 lanterns → 10 + 2 on touch devices
  • Invasion page: 120 GSAP particles (20+40+60) → 43 (8+15+20); infinite repeat: -1 ember flicker disabled on mobile

Shared utility

  • Extracted isTouchDevice() into src/utils/debounce.ts — consistent detection across all three pages

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spectrum-web-2026 Ready Ready Preview Mar 25, 2026 9:35am

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses mobile/iPad “page not responding” freezes by reducing animation/GPU load and fixing a Lenis requestAnimationFrame lifecycle leak in the landing page.

Changes:

  • Cancel Lenis RAF loop on unmount and disable Lenis entirely on touch devices.
  • Reduce canvas DPR on small screens and lower particle counts/animations on touch devices.
  • Add a shared isTouchDevice() utility used across pages.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/utils/debounce.ts Adds isTouchDevice() shared helper alongside existing debounce utility.
src/pages/landingRevamp/LandingRevamp.tsx Caps canvas DPR on small screens; cancels Lenis RAF on cleanup; disables Lenis on touch.
src/pages/invasion/Invasion.tsx Reduces particle counts and disables ember flicker animation on mobile/touch.
src/pages/events/Events.tsx Reduces sakura/lantern DOM particle counts on mobile/touch.
package-lock.json Updates dependency metadata flags for @types/react and csstype.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +17 to +18
export const isTouchDevice = (): boolean =>
window.matchMedia("(hover: none) and (pointer: coarse)").matches;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isTouchDevice() assumes window.matchMedia is always available. This will throw in non-browser contexts (SSR, some test runners) or if matchMedia is missing. Add a typeof window !== "undefined" / typeof window.matchMedia === "function" guard and a safe fallback (e.g., navigator.maxTouchPoints > 0) so callers can use it without crashing.

Suggested change
export const isTouchDevice = (): boolean =>
window.matchMedia("(hover: none) and (pointer: coarse)").matches;
export const isTouchDevice = (): boolean => {
// Guard against non-browser environments (SSR, some test runners)
if (typeof window === "undefined") {
return false;
}
// Prefer navigator.maxTouchPoints when available
if (typeof navigator !== "undefined" && typeof navigator.maxTouchPoints === "number") {
if (navigator.maxTouchPoints > 0) {
return true;
}
}
// Fallback to matchMedia when supported
if (typeof window.matchMedia === "function") {
try {
return window.matchMedia("(hover: none) and (pointer: coarse)").matches;
} catch {
// If matchMedia throws for any reason, fall through to default
}
}
// Conservative default when we cannot reliably detect touch capability
return false;
};

Copilot uses AI. Check for mistakes.
Comment on lines +298 to 301
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;
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DPR cap uses window.innerWidth < 768 to decide “mobile”, which excludes many iPads/tablets (often >= 768px wide). If the goal is to reduce canvas GPU memory on iPad too, consider basing the DPR cap on isTouchDevice() (or a max pixel budget) instead of only viewport width so tablets don’t still allocate at DPR 2.

Copilot uses AI. Check for mistakes.
@Apurv-15 Apurv-15 marked this pull request as ready for review March 26, 2026 05:21
@Apurv-15 Apurv-15 merged commit 1d08139 into main Mar 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants