One flick plays a 133-frame film, a flick back rewinds it. Scroll animation examples compared, with how my Fragrancy perfume page really works in code.
Most scroll animation examples you find online are the same three tricks: fade in on scroll, parallax layers, and a sticky section that pins while text slides past. They work, but nobody remembers them. In my Fragrancy experiment, a perfume landing page, one flick of the wheel plays a short film: the bottle glides from the hero into the product section, and a flick back rewinds it to where it started. Under the hood it is not a video tag at all. It is 133 WebP frames painted on a canvas, a tiny state machine with two states, and a looping clip that takes over once the film stops. Below I break down how it works, straight from the shipped code, compare it to the usual scroll techniques, and say when you should not build it this way.
Open the Fragrancy scroll experiment and you land on a page for a made-up fragrance called Glacier Mist: "Cool. Crisp. Ethereal." The bottle sits in a cinematic scene that moves gently, like the first second of a TV ad. Scroll once and the camera travels, the bottle settles into a second scene, and the product details arrive: top, heart and base notes, a size picker for 30, 50 and 100 ml, and an Add to bag button. Scroll up and the whole thing plays backwards.
It feels like scrubbing a video, but it is more controlled than that. You cannot stop the film halfway and leave the bottle floating in a blurry in-between frame. One gesture commits you to a destination, and the film always lands on a clean, designed composition. That single decision is what makes it read like an ad instead of a tech demo.
The tools listed on the experiment are Figma, Claude, the Figma MCP, Higgsfield and Kling video, plus what I call a scroll-played film. Figma and the Figma MCP handled the layout and type. Higgsfield and Kling produced the motion. Claude helped me write the playback code. It sits with the rest of my AI design experiments, which are all working builds, not mockups.
I read the page's own JavaScript to write this section, so these are the real mechanics, not a reconstruction.
1. The film is an image sequence, not a video. A small manifest file describes it: 133 frames, 60 fps, WebP. Each frame is fetched, turned into an ImageBitmap and drawn onto a canvas. The frame shown is simply the progress value times the frame count. Why not a video element with currentTime? Because browsers seek video to keyframes, and scrubbing backwards through compressed video stutters on a lot of devices. Individual frames rewind as cleanly as they play forward.
2. Frames load coarse to fine. The loader does not fetch frame 1, 2, 3 in order. It fetches every fourth frame first, then fills every second one, then the rest, with six downloads running in parallel. If you scroll before everything has arrived, the draw function picks the nearest frame that has loaded. So on a slow connection the film plays at a lower frame rate instead of freezing. I like this one a lot because it is a design decision disguised as an engineering one.
3. Before any frames exist, two stills crossfade. There are two keyframe images, one per scene. Until the sequence is ready, the canvas crossfades between them with a smoothstep curve and a tiny 6% zoom, so the transition still has motion. The user never sees an empty box.
4. Scroll is a trigger, not a scrubber. The page listens for wheel, arrow keys, the space bar and touch swipes. Wheel deltas add up, and the counter resets after a short pause, so a trackpad's long tail of tiny events does not fire twice. Past a small threshold it calls go(1) or go(0). A requestAnimationFrame loop then moves progress toward the target at a constant speed, so the full film takes its natural length (133 frames at 60 fps, a bit over two seconds) in either direction. The copy for the next section animates in when progress passes 80%, not at the end, so text and bottle arrive together.
5. A loop takes over at rest. Each scene has its own short looping video. When the film settles, the matching loop restarts from zero, and only after two animation frames does the canvas hide. That double frame wait is what stops a flash between the last film frame and the first loop frame. The result is a page that is never fully still, which is the thing that makes it feel expensive.
On a phone, the layout also interpolates the frame's position and scale between two focus points as the film plays, so the bottle stays above the product panel instead of hiding behind it.
Here is how this approach stacks up against the scroll animation examples you will see in most galleries. The right answer depends on what the page is selling.
| Technique | How it is built | Best for | Main cost |
|---|---|---|---|
| Fade or slide in on scroll | CSS transitions plus an IntersectionObserver | Docs, blogs, SaaS feature lists | Forgettable, easy to overuse |
| Parallax layers | Layers moving at different speeds | Storytelling heroes, travel, games | Motion sickness for some users |
| Scrubbed video (currentTime) | A video element seeked by scroll position | Quick prototypes | Stutters on reverse, keyframe limits |
| Free-scrub image sequence | Canvas frames mapped 1:1 to scroll | Product reveals, Apple-style pages | Users stop on ugly in-between frames |
| Gesture-triggered film (Fragrancy) | Canvas frames played to a fixed target, loops at rest | Luxury, fashion, single-product pages | Heavy assets, only a few states |
| Real-time 3D or shaders | three.js or WebGL scenes driven by scroll | Configurators, interactive products | Build time, GPU cost |
My pick for a single hero product with a strong art direction is the gesture-triggered film. For a page with ten sections and a lot of reading, it is the wrong tool. Use quiet reveals and spend the motion budget on one moment.
Fragrancy is not the only scroll experiment I have shipped, and each one makes a different trade-off:
If you liked the idea of a film that responds to touch, the card you shatter to unfreeze uses a related trick with alpha video. And if you want scroll to drive real 3D instead of pre-rendered frames, the 3D gummy bear configurator shows the opposite end of the table above.
A page like this used to need a 3D artist, a motion designer and a front-end developer. Here, AI video from Higgsfield and Kling produced the cinematic motion, and Claude helped write the playback code. That collapses the team into one person who can move between design and code, which is what I mean when I talk about being a design engineer.
What AI did not decide: that scroll should commit to a destination instead of scrubbing freely, that copy should arrive at 80% instead of 100%, that slow connections should get fewer frames instead of a spinner, that the page should never be fully still. Those are product design calls. The generated film is the raw material. The feel comes from rules like these, and you only find them by building and playing with the thing.
The same lesson showed up in very different work. On ViewStats, where I led design for the MrBeast creator platform, I moved into the codebase and opened pull requests myself. Shorter loops between idea and working code changed what we could try. A scroll film is a small version of that: you cannot judge it in a static frame, only in the browser.
Be honest about the costs before you copy it.
It is heavy. 133 full-screen frames plus two loop videos is a lot to download for a landing page, even with the coarse-to-fine loader. On a content site that lives on organic search, that weight can hurt you more than the motion helps.
It hijacks scroll. Calling preventDefault on the wheel means the browser no longer scrolls the page normally. That is fine for a two-state product showcase. It is a bad idea for anything people need to read, search or skim, and it needs keyboard support (this build has it) plus a reduced-motion path in production.
It fits few states. Two scenes are easy. Ten scenes means ten films, ten loops and a lot of asset management. At that point real-time 3D or simple reveals are usually the better choice.
For a single product, a short film or 3D move that lands on a clean composition beats many small effects. Fragrancy plays one film per scroll gesture. For pages with many sections, subtle reveals plus one standout moment work better than animating everything.
Export the video as an image sequence, draw frames on a canvas, and pick the frame from a progress value. Load frames coarse to fine and fall back to the nearest loaded frame, so it degrades to a lower frame rate instead of freezing. Seeking a video element backwards is where most stutter comes from.
AI video tools can generate the motion itself, like the bottle move in Fragrancy, and coding assistants can write the playback loop. The decisions that make it feel good, such as when to trigger, how long to play and what happens at rest, still need a designer who tests it in the browser.
For reading-heavy pages, yes. For a short showcase with two or three states, controlled scroll can work if it responds instantly, supports keyboard and touch, and respects reduced motion settings.
If you are launching a product and want a landing page or app moment people actually share, here is my checklist: pick one hero moment, decide its states before you generate any motion, design the slow-connection version, and make sure the page is alive at rest. If you would rather have someone do it end to end, from Figma to AI motion to shipped code, you can hire me as your product designer or browse my product design work first.
If you have a web or mobile app idea in mind or you need some advice about product design, feel free to get in touch ✉️