Writing

Scroll-driven video in Webflow, and the four things that break

I wanted 3D letters flying at the screen on scroll, the way that North Face ad does it. Getting the animation was the easy half. Getting a browser to scrub a video frame by frame, at a quality worth looking at, took three dead ends and a specific encode.

The finished scroll-driven 3D animation
Topic
Motion on the web
Tools
Blender, After Effects, Webflow
Stack
GSAP, ScrollTrigger, FFMPEG
Published
June 2024

Two of the three obvious routes in Webflow do not work for this, and neither fails in a way that tells you why. Writing down where each one stops, and what the working version actually needs, is most of the value in this post.

Building the animation

The text was set in Illustrator in Bubblegum from dafont, then expanded so it could leave as an SVG and come into Blender as real geometry rather than a font reference.

The inflated look is cloth physics with collisions. The letters start slightly ahead of the logo outline, so they expand into the shape before they fly forward. That offset matters: with the text sitting inside the outline it gets absorbed instead of inflating, which took a while to diagnose and is fixed by raising the collision’s outer thickness.

Rendered as a PNG sequence with alpha, taken into After Effects for a white background, and exported to mp4.

The type set in Illustrator, expanded so it can leave as an SVG
The type set in Illustrator, expanded so it can leave as an SVG
The same letters in Blender, inflated and flat side by side
The same letters in Blender, inflated and flat side by side
The letters positioned against the logo outline they expand into
The letters positioned against the logo outline they expand into
The cloth settings. Thickness Outer is the one that stops the absorption
The cloth settings. Thickness Outer is the one that stops the absorption

Dead end one: the background video element

The first attempt did not even reach the scrolling part. Webflow’s background video element compresses hard, and at full width the result is not worth shipping. This is not a setting you have missed. The compression is applied on upload and there is no way around it from inside Webflow.

The Webflow forum thread confirming the compression is unavoidable, and pointing at Dropbox
The Webflow forum thread confirming the compression is unavoidable, and pointing at Dropbox

Dead end two: Lottie

Lottie handles scroll-linked playback properly and Webflow supports it directly, so this looked like the answer. The problem is file size. Anything past 15 to 20MB is already too heavy to serve, and this animation came out at 45MB after compression. A JSON file is the wrong container for something with this much per-frame detail.

What worked, and the encode it needs

The working approach scrubs a real video file against scroll position with GSAP and ScrollTrigger. There is a Codepen by Shaw that does exactly this, and the important part is buried in its comments: the video has to be encoded for it, or seeking is not frame-accurate and the scrub stutters.

That means FFMPEG. The flags that matter are -movflags faststart, so playback can begin before the whole file arrives, and -g 1, which makes every frame a keyframe so any frame can be sought directly. On Windows, add the FFMPEG bin folder to the Path system variable first, or the command is not found.

ffmpeg -i "D:/test/whitebg-1080p.mp4" -vf scale=1920:1080 -movflags faststart -vcodec libx264 -crf 20 -g 1 -pix_fmt yuv420p "D:/test/output.mp4"

Quote both paths and keep them short. Editing the Codepen’s own command in place, with an unquoted path and the directory split from the filename, is what was throwing errors.

The Codepen JS, with the encoding requirement written in the comments
The Codepen JS, with the encoding requirement written in the comments
Adding the FFMPEG bin folder to the Path variable on Windows
Adding the FFMPEG bin folder to the Path variable on Windows

Two more failures worth knowing about

With the encoded file on Dropbox the video still would not render. A Dropbox share link ends in dl=0, which serves a preview page rather than the file. Changing it to raw=1 serves the file itself and the element starts working.

Then the script threw an error on currentSrc being null. The Codepen JS uses querySelector to find the video, and placing that script in the page head means it runs before the element it is looking for exists. Moving the script into the same HTML embed, after the video element, fixes it. GSAP and ScrollTrigger have to load ahead of it.

<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.9.1/ScrollTrigger.min.js"></script>

Both of these load before the Codepen script, not after it.

Inside the embed: the video element first, then the script that drives it
Inside the embed: the video element first, then the script that drives it

The result

This is the finished thing, running on the old palle.ca homepage. The letters inflate into the mark and fly at the screen, and every frame of it is tied to scroll position rather than to a clock.

The finished scroll-driven animation, as it ran on the homepage

Where it still falls short

Dropbox is the weak link. It serves the file, but not at a quality that does the render justice, and it is not meant to be a video host. Vimeo was the obvious alternative and appears to re-encode on upload, which undoes the FFMPEG work the whole technique depends on. Somewhere that serves the exact bytes you uploaded is what this needs, and that generally costs money.