Vibes DIY
Vibes DIY / Blog
From the build log

One pixel of fake scroll

The bug report was one sentence: "Tapping the top of the screen should scroll the page to top. The iframe is not receiving that tap."

It never will. The iOS scroll-to-top gesture is a WebKit-native behavior — tap the status bar, and Safari scrolls the main frame's scroller to the top. It isn't exposed as a JavaScript event, and it is never forwarded into a cross-origin subframe. Every Vibes app runs in exactly that: a position: fixed, viewport-filling, cross-origin iframe. So the parent document has nothing to scroll, the app's real scroller is unreachable by the gesture, and the tap lands on nothing.

You can't intercept an event that doesn't exist. But you can build a tripwire out of the one thing the gesture does do.

Branded title card: “scrollTop: 1 → 0” over a duotone photo of an iPhone home screen, status bar at the top.
The gesture can't be heard — but it can be caught moving one pixel.

Park the page at 1

The trick, shipped in PR #3005 and living in useStatusBarScrollToTop.ts: make the parent document scrollable by exactly one pixel, and park it at scrollTop = 1.

  • A hidden sentinel (visibility: hidden, pointer-events: none, height calc(100lvh + 2px)) gives the document a sliver of overflow — just enough that "scroll to top" is a real operation again.
  • The fixed iframe swallows every touch, so the user can never scroll the parent themselves. The only thing in the world that can drive the parent's scroll from 1 to 0 is the native status-bar tap.
  • A scroll listener watches for the 1 → 0 transition, posts a vibe.evt.scroll-to-top message over the existing sandbox postMessage bridge, and re-parks at 1 — with a parking flag so the programmatic re-park isn't mistaken for another tap.

Get posts like this in your inbox

One email field. Real updates. No algorithm required.

Inside the iframe, the runtime scrolls whatever the app actually scrolls: the document scroller, plus any scrolled container at least 60% of the viewport tall. That covers both body-scrolling apps and the common h-screen + inner overflow-auto layout — while leaving small scrolled widgets (an inner list, a code block) exactly where the user left them.

Invisible by construction

A hack like this earns its keep by being unobservable when it isn't firing.

The 1px park never paints. The app iframe is position: fixed, and fixed elements don't move with parent scroll. Parking the document at scrollTop = 1 moves nothing the user can see — the sentinel itself is hidden and untouchable.

Desktop never grows a scrollbar. On browsers with permanent scrollbars, one pixel of body overflow means a visible scrollbar gutter — a real regression in service of a gesture that doesn't exist there. So the hook gates on iOS user agents, with the iPadOS wrinkle handled explicitly: iPadOS ≥13 reports platform === "MacIntel", and only maxTouchPoints > 1 separates it from an actual Mac.

Old WebKit degrades to inert, never to broken. lvh — the viewport height with the URL bar collapsed, which is what guarantees overflow in every URL-bar state — arrived in WebKit 15.4. The hook sets a 100vh + 2px fallback line first, so pre-15.4 devices keep a best-effort sliver of overflow; and if even that fails to overflow, the parent simply never scrolls and the relay never fires. The failure mode is "the feature silently doesn't exist," which is exactly what those devices had before.

The embed route deliberately doesn't get this. An embedded vibe's host page owns the main frame — our /embed document is itself a subframe, so a sentinel there would be as unreachable as the app. Only the full-screen viewer route wires the relay. Knowing where a trick can't work is part of the trick.

The shape of the fix

What makes this one worth writing down isn't the pixel — it's the pattern. The platform primitive you want (a "status bar tapped" event) doesn't exist, and no amount of listening will conjure it. But the gesture has one observable side effect: it moves the main frame's scroll position to zero. So you arrange the world so that side effect can only mean one thing — no other actor can move that number — and the side effect becomes the event.

One pixel of state, owned entirely by the gesture you're trying to hear.

Build an app this afternoon

Vibes DIY turns a prompt into a live, shareable app — with the fit and finish sweated for you, one pixel at a time.

Start building →

Enjoyed this? Get the next post by email

One email field. Real updates. No algorithm required.

Prefer a feed? RSS · Atom