Skip to content

Commit 3caad76

Browse files
kuco23claude
andcommitted
claude:perf(hero): enable rune canvas on phones, gate RAF on intersection + page visibility
Co-Authored-By: Claude <noreply@anthropic.com>
1 parent b3fd3aa commit 3caad76

2 files changed

Lines changed: 55 additions & 20 deletions

File tree

src/components/sections/hero.scss

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,25 +24,24 @@
2424
// the initial screen height so it doesn't keep growing on long pages);
2525
// cells inside the rune silhouette render brighter, painting the
2626
// Stakecore mark as a lighter ASCII shape embedded in the dimmer field.
27+
// Renders at every breakpoint — the RAF loop is gated by
28+
// IntersectionObserver + page visibility so off-screen / backgrounded
29+
// instances don't burn cycles on phones.
2730
.hero-rune-canvas {
28-
display: none;
29-
30-
@include t.up(md) {
31-
display: block;
32-
position: absolute;
33-
top: 0;
34-
left: 0;
35-
width: 100%;
36-
height: 100vh;
37-
opacity: 0.3;
38-
pointer-events: none;
39-
z-index: -1;
40-
// Fade the lower half toward the page background so the wave
41-
// dissolves into the dark body instead of cutting off at the
42-
// hero's overflow edge.
43-
mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%);
44-
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%);
45-
}
31+
display: block;
32+
position: absolute;
33+
top: 0;
34+
left: 0;
35+
width: 100%;
36+
height: 100vh;
37+
opacity: 0.3;
38+
pointer-events: none;
39+
z-index: -1;
40+
// Fade the lower half toward the page background so the wave
41+
// dissolves into the dark body instead of cutting off at the
42+
// hero's overflow edge.
43+
mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%);
44+
-webkit-mask-image: linear-gradient(to bottom, transparent 0%, black 25%, black 75%, transparent 100%);
4645
}
4746

4847
.hero .container {

src/components/sections/heroRuneCanvas.tsx

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ const HeroRuneCanvas = () => {
242242
let last = 0
243243
let raf = 0
244244
let ready = false
245+
// RAF only runs when the canvas is in-viewport AND the tab is in
246+
// the foreground. Start optimistic so we don't delay the first
247+
// paint while waiting for the IntersectionObserver to fire.
248+
let intersecting = true
249+
let pageVisible = !document.hidden
245250
const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false
246251

247252
const drawFrame = () => {
@@ -261,12 +266,41 @@ const HeroRuneCanvas = () => {
261266
drawFrame()
262267
}
263268

269+
const startLoop = () => {
270+
if (raf !== 0) return
271+
if (reduceMotion || !intersecting || !pageVisible) return
272+
// Reset the time-since-last so the first frame after a resume
273+
// doesn't see a huge dt and jump the wave forward.
274+
last = 0
275+
raf = requestAnimationFrame(tick)
276+
}
277+
278+
const stopLoop = () => {
279+
if (raf === 0) return
280+
cancelAnimationFrame(raf)
281+
raf = 0
282+
}
283+
264284
setup()
265285
rasterize().then(() => {
266286
ready = true
267287
if (reduceMotion) drawFrame()
288+
else startLoop()
268289
})
269-
if (!reduceMotion) raf = requestAnimationFrame(tick)
290+
291+
const io = new IntersectionObserver(([entry]) => {
292+
intersecting = entry.isIntersecting
293+
if (intersecting) startLoop()
294+
else stopLoop()
295+
})
296+
io.observe(canvas)
297+
298+
const onVisibility = () => {
299+
pageVisible = !document.hidden
300+
if (pageVisible) startLoop()
301+
else stopLoop()
302+
}
303+
document.addEventListener('visibilitychange', onVisibility)
270304

271305
const onResize = () => {
272306
setup()
@@ -277,7 +311,9 @@ const HeroRuneCanvas = () => {
277311
window.addEventListener('resize', onResize)
278312

279313
return () => {
280-
cancelAnimationFrame(raf)
314+
io.disconnect()
315+
document.removeEventListener('visibilitychange', onVisibility)
316+
stopLoop()
281317
window.removeEventListener('resize', onResize)
282318
gl.deleteTexture(glyphTex)
283319
gl.deleteTexture(runeTex)

0 commit comments

Comments
 (0)