SH
I
EMO
← All projects

Swoop.

A camera drone store where scrolling flies you through five aerial videos.

Swoop on desktop
Role
Design & development
Timeline
2 days
Type
Concept project
Stack
Next.jsTypeScriptCanvas API

The challenge

Scroll-scrubbed video sounds simple until you build it. Browsers can't seek a video frame-accurately on every scroll tick, so playback stutters, and reversing direction makes it worse.

Swapping video for images fixes the seeking, but five 1080p clips are hundreds of frames. Loading and decoding all of them would blow past a phone's memory and take ages on mobile data. The page had to feel like flying, in both scroll directions, without that cost.

How it works

Every frame you see takes this path, from source footage to the screen:

Build timeIn the browser
Drone footage
ffmpeg extracts every frame
sharp encodes each frame as AVIF
1920×1080 desktop set
9:16 cropped phone set
Scheduler fetches and decodes only frames near the scroll
Canvas draws each frame, with whip blur between clips
Build timeIn the browser
Drone footage
ffmpeg extracts every frame
sharp encodes each frame as AVIF
1920×1080 desktop set
9:16 cropped phone set
Scheduler fetches and decodes only frames near the scroll
Canvas draws each frame, with whip blur between clips

Key decisions

  1. 01

    Turn the videos into image sequences

    A build script pipes each clip through ffmpeg into sharp and encodes every frame as AVIF, at around 90 KB per 1080p frame. It also writes a second, 9:16 centre-cropped set for phones, so mobile never downloads pixels it can't show.

  2. 02

    Only decode what's near the scroll position

    A custom scheduler streams frames in the background, six requests at a time, but only decodes full-resolution frames inside a window around where you are: 40 ahead and 16 behind, flipping with scroll direction. Frames that leave the window are released. Every fourth frame also gets a small preview, so a fast fling always has something to draw while full frames catch up.

  3. 03

    Whip-blur transitions on one canvas

    Clips join with a whip pan instead of a cut. The outgoing frame is drawn eight times with growing offsets and fading opacity to fake motion blur while the next clip slides in, all on a single canvas with no video elements.

Code from lib/frameScheduler.ts:
const AHEAD = 40;
const BEHIND = 16;

const inWindow = (g: number, margin = 0) => {
  const back = (direction > 0 ? BEHIND : AHEAD) + margin;
  const forward = (direction > 0 ? AHEAD : BEHIND) + margin;
  return g >= focus - back && g <= focus + forward;
};

const distance = (g: number) => {
  const d = (g - focus) * direction;
  return d >= 0 ? d : -d * 2;
};

Decision 02, in code

lib/frameScheduler.ts

  • Lines 1–2

    How far the window reaches: 40 frames ahead of you, 16 behind.

  • Lines 4–8

    inWindow flips the window when you scroll back up, so the frames you're heading towards are always the ones ready.

  • Lines 9–12

    distance ranks frames for loading. Frames behind you count double, so whatever comes next always loads first.

Results

  • 459frames scrubbed by scroll across 5 clips
  • 90 KBaverage per 1080p AVIF frame
  • 56frame decode window, so memory stays capped
  • 9:16lighter frame set served to phones

Screens

Swoop on desktop
Swoop on mobile