From cf67af71312cada910b85371e8329944b7836ecc Mon Sep 17 00:00:00 2001 From: artheru Date: Wed, 10 Jun 2026 08:26:48 +0800 Subject: [PATCH] perf: pause tavern canvas when off-screen (0.60.0) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - the 60fps canvas render loop ran continuously even when scrolled out of view (the home page is long now — wasted CPU/battery while reading below) - IntersectionObserver pauses the rAF loop at 0% visibility and resumes it when the tavern scrolls back in; cleaned up on unmount - verified live: ~60 rAF/s on-screen → 0 off-screen → 60 on resume, 0 errors --- index.html | 2 +- package.json | 2 +- src/Scene2D.jsx | 2 +- src/TavernGame.jsx | 11 +++++++++-- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/index.html b/index.html index f0194d2..0fd6071 100644 --- a/index.html +++ b/index.html @@ -4,7 +4,7 @@ - + diff --git a/package.json b/package.json index 624f041..43e16ab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.59.0-a11y", + "version": "0.60.0-canvasperf", "private": true, "type": "module", "scripts": { diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index c8b40cf..dfbf13d 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -7,7 +7,7 @@ import Account from './Account.jsx'; import TavernGame from './TavernGame.jsx'; import { api } from './api.js'; -const RELEASE = '0.59.0-a11y'; +const RELEASE = '0.60.0-canvasperf'; const NAV = [ { id: 'home', label: 'Tavern' }, diff --git a/src/TavernGame.jsx b/src/TavernGame.jsx index c5888d5..a7a4832 100644 --- a/src/TavernGame.jsx +++ b/src/TavernGame.jsx @@ -57,12 +57,13 @@ export default function TavernGame({ onHotspot, stats }) { useEffect(() => { statsRef.current = stats; }, [stats]); const [ready, setReady] = useState(false); useEffect(() => { - const keys = Object.keys(SRC); const S = {}; let loaded = 0, raf, f = 0, buf; + const keys = Object.keys(SRC); const S = {}; let loaded = 0, raf, f = 0, buf, paused = false, io; const start = () => { setReady(true); buf = document.createElement('canvas'); buf.width = W; buf.height = H; buildStatic(buf, S); const ctx = canvasRef.current.getContext('2d'); ctx.imageSmoothingEnabled = false; const render = () => { + if (paused) return; f++; ctx.drawImage(buf, 0, 0); // bartender (a clothed guard tending bar) behind the bar (idle bob) @@ -107,9 +108,15 @@ export default function TavernGame({ onHotspot, stats }) { raf = requestAnimationFrame(render); }; render(); + // pause the 60fps loop while the tavern is scrolled out of view (CPU/battery) + io = new IntersectionObserver(([e]) => { + if (e.isIntersecting) { if (paused) { paused = false; render(); } } + else if (!paused) { paused = true; cancelAnimationFrame(raf); } + }, { threshold: 0 }); + io.observe(canvasRef.current); }; keys.forEach((k) => { const im = new Image(); im.onload = () => { if (++loaded === keys.length) start(); }; im.src = SRC[k]; S[k] = im; }); - return () => cancelAnimationFrame(raf); + return () => { cancelAnimationFrame(raf); if (io) io.disconnect(); }; }, []); return (