Pinning
Pinning is a ScrollTrigger feature that locks an element in place while scroll progresses past it. The element appears to stay still while content scrolls underneath, until ScrollTrigger reaches the `end` position and releases. It's the engine behind sticky hero scenes, scroll-driven case studies, and the `pin a section, scrub content inside it` pattern.
Updated June 1, 2026
How pinning works
Set pin: true on a ScrollTrigger and the trigger element holds its position from start to end. ScrollTrigger inserts a placeholder of the same height into the document flow so nothing else jumps, then sets the element to position: fixed for the pin duration. When scroll exits the range, the element returns to its normal flow.
The combination above is the classic horizontal-scroll-inside-a-pinned-section pattern. Pin holds the scene; scrub binds the inner tween to the same 2000px scroll range.
Reserving scroll space
By default ScrollTrigger adds pinSpacing (a placeholder) so the layout below the pinned element doesn't jump. Set pinSpacing: false only when the pinned element is position: absolute or otherwise removed from flow, or when you intentionally want surrounding content to scroll under the pinned one without reserving space.
Common bug
pinSpacing: true is doing exactly what it should - reserving the scroll distance. The pinned element renders at its normal height, plus a placeholder spacer matching the pin duration.Use pinning for
- Hero scenes that hold while a tagline scrubs in
- Horizontal scroll sections inside a vertical page
- Case-study sections where the visual holds while copy advances
- Side-by-side reveals where one side pins and the other scrolls past
- Long-form data stories with a sticky visualization and scrolling captions
Use something else when
- Sticky navigation or sidebar - native CSS
position: stickyis lighter and doesn't need GSAP - Short reveals on enter - regular ScrollTrigger without pin is enough
- Mobile (touch devices) where pinning often clashes with momentum scroll - gate via
gsap.matchMediaand ship a non-pinned variant under(max-width: 768px)
Used in these Annnimate components
- The Parallax component pins the scene wrapper and scrubs layer transforms across the pin range
- The Multi Flip component pins the gallery and orchestrates a sequence of flip cards inside the pinned range
- The Popping Text component pins the headline and stagger-reveals characters as scroll progresses
- The Image Dissolve Scroll shader pins the canvas and scrubs the dissolve uniform 0->1 across the pin
See it running in production
Common questions
- Why does my pinned section jump on resize?
- ScrollTrigger caches the pin's start/end positions at creation. After a resize the layout shifts, but the cached values don't. Call
ScrollTrigger.refresh()after the resize settles (debounced) so it recalculates. For pinned sections with content that may reflow, setinvalidateOnRefresh: trueon the trigger. - Should I use pinType: 'fixed' or 'transform'?
- Default is
pinType: 'fixed'which usesposition: fixed. Switch topinType: 'transform'when the pinned element is inside a scrollable ancestor that isn't the window (e.g. an inner overflow-auto div).fixedignores the inner scroller and visually breaks;transformrespects it. - How do I pin only on desktop?
- Wrap the ScrollTrigger creation in
gsap.matchMedia()with the breakpoint you want. Inside the desktop branch, create the ScrollTrigger withpin: true; inside the mobile branch, omit the pin (or skip the trigger entirely). matchMedia auto-reverts the desktop branch on a resize down to mobile. - Can I pin multiple sections in a row?
- Yes - each section gets its own ScrollTrigger with its own
start,end, andpin: true. Just confirm thepinSpacingon each is correct so the natural document height accounts for all pin ranges. Two sections pinning for 2000px each add 4000px of scroll distance to the page. - Why does pinning break inside Annnimate's auth'd (app) chrome?
- The (app) tree uses native scroll on an inner overflow-auto div, not on window. Pin defaults to
position: fixedagainst the viewport, which ignores the inner scroller. Embed pinned components only on marketing/public surfaces, or setpinType: 'transform'and confirm the ScrollTrigger is wired to the inner scroller viascrollerProxy. See the scroll architecture rule.
ScrollTrigger.refresh().NextScroll snappingScroll snapping is a ScrollTrigger feature that eases the scroll position to the nearest defined stop after the user stops scrolling.