Skip to content

Fix the timeline background on mobile: parallax gap, dead renderer after rotation, stale canvas size - #16

Merged
LucaDeLeo merged 2 commits into
mainfrom
lucadeleo/investigate-image-issue
Sep 14, 2026
Merged

LucaDeLeo merged 2 commits into
mainfrom
lucadeleo/investigate-image-issue

Conversation

@LucaDeLeo

Copy link
Copy Markdown
Contributor

Reported by Tomás: the About page looks half broken on an iPhone 16, "creo que es por el background, el parallax hace algo raro". That read was right. Reproduced on production, and found two more defects in the same component while verifying.

1. The parallax slid the canvas off the bottom of the page

timeline-threads.tsx translates the fixed background canvas up by scrollY * 0.02, and the container reserved a flat 10vh for that shift, with this comment:

10vh covers typical scroll depths (10vh / 0.02 = 500vh of page scroll)

That budget is five viewport heights. The About page is 21,700–29,200px tall at 390px wide — 26 to 34 viewports. Past scrollY > 5 × innerHeight (~4,200px, the first 15% of the page) the canvas moved further than its buffer and a flat #f5f5f5 band crept up from the bottom of the screen. That hard line is what Tomás screenshotted.

Measured on baish.com.ar, blank band at the bottom of the viewport:

scrollY before after
3,949 0px 0px
7,166 ~60px 0px
16,000 268px 0px
max (20,396) ~430px of 844px 0px

Fixed by clamping the offset to the reserved buffer. PARALLAX_FACTOR and PARALLAX_BUFFER_VH now live at module scope so the scroll handler and the container height cannot drift apart.

2. Rotating the phone killed the background permanently

A threadCount change calls setThreads([]), which unmounts the canvas. The renderer-init effect depended on [shouldAnimate, blurStdDeviation, frameInterval] — not on threads — so when the replacement canvas mounted, the effect never re-ran and the element was never transferred to a worker. Nothing drew on it again.

threadCount changes at widths 480 / 640 / 820 / 1440 / 1920, so a rotation (390 → 844) crosses three of them.

step before after
1500×800 → 1500×900 (height only) alive alive
1500 → 1300 wide (crosses 1440) dead alive
1300 → 900 wide dead alive
rotate 390 ↔ 844 dead alive

Fixed by depending on threads.length and keying the canvas, so each re-init gets a fresh element — transferControlToOffscreen() only works once per canvas.

3. The canvas was never resized

The backing store is sized once at transfer time and neither renderer reads the box again, so any resize that left the performance profile alone (a plain window drag, or one staying inside a thread-count bucket) stretched a stale bitmap across the new box. A ResizeObserver now rebuilds the renderer when the box diverges from the size it was built at, with a 250ms settle so a drag doesn't respawn a worker every frame.

Verified no spurious rebuilds: idle and heavy scrolling both leave the canvas untouched. This matters on iOS, where the URL bar collapsing changes innerHeight — the container is sized in vh, which doesn't track that, so the box stays stable.

Also

bunx tsc --noEmit had one pre-existing error: course-opportunities.test.ts imports bun:test with no types installed. Added @types/bun. TypeScript 6 here does not auto-include node_modules/@types (confirmed with --traceResolution), so "types": ["bun"] in tsconfig is needed on top of the install.

Checks

  • bunx tsc --noEmit — 0 errors (was 1)
  • bun run lint — clean
  • bun test — 6 pass
  • bun run build — succeeds
  • No horizontal overflow on any /es page at 390px
  • Reduced-motion path: parallax no-ops, no gap
  • Confirmed the types array didn't mask real checking — a deliberate CSSProperties error was still caught

Known, not addressed

The parallax now saturates after ~4,200px of scroll and holds. The alternatives are worse: scaling the offset to scroll progress makes the effect strength depend on page length (4× stronger on a two-screen page than on About), and growing the buffer costs mobile fill-rate on every page, which this component is tuned hard to avoid.

🤖 Generated with Claude Code

LucaDeLeo and others added 2 commits September 14, 2026 19:06
Three defects in the fixed background canvas, all reported as "the About
page looks broken on mobile".

Parallax ran off the page. The canvas is translated up by scrollY * 0.02
while its container reserves a flat 10vh to cover the shift - a budget of
five viewport heights. The About page is ~26 viewports on a phone, so past
~4,200px of scroll the canvas slid out from under the viewport and left a
hard-edged #f5f5f5 band, reaching ~430px of an 844px screen at full scroll.
Clamp the offset to the reserved buffer, and drive both the buffer and the
factor from shared constants so they cannot drift apart again.

The renderer died on rotation. A threadCount change resets threads to [],
which unmounts the canvas; the init effect did not depend on threads, so
the replacement element was never handed to a worker and nothing drew on it
again. A phone rotation crosses three threadCount thresholds, so rotating
once blanked the background until reload. Depend on threads.length, and key
the canvas so every re-init gets a virgin element - transferControlToOffscreen
only works once per canvas.

The canvas was never resized. Its backing store is sized once, at transfer
time, and neither renderer reads the box again, so any resize that left the
performance profile alone stretched a stale bitmap across the new box. Watch
the box with a ResizeObserver and rebuild when it diverges from the size the
renderer was built at, settling for 250ms so a window drag does not respawn
a worker every frame.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
course-opportunities.test.ts imports from "bun:test", which had no types
installed, leaving one error in bunx tsc --noEmit. TypeScript 6 in this
project does not auto-include node_modules/@types, so naming the package
explicitly is required on top of installing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Sep 14, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated
nextbaish Ready Ready Preview Sep 14, 2026 6:07pm UTC

Request Review

@LucaDeLeo
LucaDeLeo merged commit c04bcff into main Sep 14, 2026
3 checks passed

This branch was successfully deployed

1 active deployment
Preview — 8b9579a8 Deployed Sep 14, 2026 by vercel[bot]
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.

1 participant