Every visitor arrives already knowing how to scroll. No tutorial, no onboarding — it's the one interaction you get for free. Which is exactly why it's worth designing deliberately: if scroll is the input everyone brings, then scroll is where the story should live.
Beats, not sections
I stopped thinking in "sections" a while ago. Sections are layout. What a scroll experience actually has is beats — moments where something changes and the visitor feels progress. A pinned hero that opens like a door is a beat. A number counting up is a beat. The gap between them is pacing.
If a beat doesn't earn its scroll distance, cut it. Nobody has ever complained that a website respected their time.
Scrub vs. trigger
The most important decision for every animation: is it scrubbed (tied to scroll position, reversible, the visitor is the playhead) or triggered (fires once when an element enters the viewport)? Scrub for spatial transitions — portals, horizontal tracks, parallax. Trigger for content reveals. Mixing them up is why so many scroll sites feel seasick.
// scrubbed: the visitor drives the playhead
gsap.timeline({
scrollTrigger: {
trigger: "#hero-pin",
start: "top top",
end: "+=400%",
scrub: 0.6,
pin: true,
},
});
// triggered: fires once, plays on its own clock
gsap.from(".card", {
y: 40,
opacity: 0,
scrollTrigger: { trigger: ".card", start: "top 85%" },
});The pacing checklist
- One scrubbed set piece per page — more and none of them feel special.
- Triggered reveals under 0.8s; the visitor is already moving on.
- Every pin needs an exit: show people the story continues below.
- Test at maximum scroll speed. Flick the trackpad. Does it still read?
The intro on this site — droplet, splash, name, city, portal — is five beats in about four seconds of scroll. It went through maybe fifteen versions, and almost all of the work was deleting things. That's the real craft of scroll storytelling: not adding motion, but cutting it until what remains feels inevitable.