
THE UNDERTOW — a limited-series site where scroll is the camera
A scroll-film site for a fictional limited series, The Undertow. As you scroll, a cliff road sinks into the sea and the titles keep the same breath. Live at the-undertow.pages.dev.
The problem
A normal landing page is a stack of cuts. This one had to treat one line of scroll as one camera move. 956 frames play in lockstep with copy and a depth meter. Reverse scrolling could not desync the picture, and Chrome could not kill the renderer mid-scroll.
Two ways to make a scroll film
- Scroll world — pre-render a continuous flight and bind it to scroll. The camera enters each scene from outside and never cuts. Space first.
- Scroll film — author a timeline in code. Scroll progress is the playhead; titles, depth, and the seam ride the same axis. Faster to revise, and you fight browser memory directly.
Why a scroll world does not cut
The trick is not a transition. It is one continuous flight. Shoot each room separately and fade them and the join shows. The camera has to slide from outside, through the interior, and on into the next space so scroll feels like travel, not a dissolve.
A scroll film is a playhead
The implementation is small. Build a tall track, map progress 0..1 to a frame index, then hang titles and a depth meter on that same number.
- Fingers own progress; the playhead eases behind. On reverse it catches up faster so copy does not drift.
- Prefetch follows the finger, not the inertial head, so the loader does not fetch the wrong window.
- Decoding a JPEG on first paint blocks the main thread, so only the nearby window is turned into
ImageBitmaps and the rest is dropped.
Lenis, GSAP, parallax
The demo is vanilla. The production tools split by job.
- Lenis smooths the input. It is not a camera.
- GSAP + ScrollTrigger bind that progress to a timeline so titles, masks, and the seam share the frame index.
- Parallax sells depth by sliding layers at different speeds. It does not replace a continuous flight, and more layers bring the same memory problem back.
Chrome error 11
A tab death mid-scroll is not a JS exception. It is a renderer SIGSEGV. Keep all 956 frames decoded and you hold about 2.3GB; animate a viewport-oversized SVG feTurbulence grain and the GPU joins in. The fix was to evict frames outside a sliding window, replace the grain with a PNG tile, and cap the canvas at 1600 CSS pixels wide.
Stack
- HTML, CSS, vanilla JS. No build.
- 956 JPEG frames on a sticky full-bleed canvas.
- Fraunces / Source Sans 3 (OFL).
- Hosted on Cloudflare Pages · the-undertow.pages.dev