Scroll storytelling with GSAP without trapping the reader
Scroll-driven storytelling can be the most memorable part of a site or the most frustrating. The difference is control: the reader must always feel that their scroll drives the story, never the other way around.
Pin with purpose
Pin a section only when something inside it changes meaningfully while the reader scrolls. Keep pinned distances proportional to the content, usually one to two viewport heights.
// Pin a section and scrub a timeline with it
const tl = gsap.timeline({
scrollTrigger: {
trigger: ".chapter",
start: "top top",
end: "+=150%",
pin: true,
scrub: true
}
});
tl.from(".chapter__title", { yPercent: 100 })
.to(".chapter__visual", { scale: 1.4, rotate: 8 }, "<");Respect the escape hatch
Keyboard users, anchor links and the back button must all work. Test jumping into the middle of a pinned section from a link, and make sure the state is correct when you land.
Clean up after yourself
Create triggers inside a context or matchMedia so they can be reverted when the layout changes. Duplicate triggers are the most common cause of jumpy, broken scroll experiences.