Back to Blog
Back to Blog
Tutorials·GSAP·April 19, 2026·9 min read

GSAP ScrollTrigger Examples: 10 Scroll Animations You Can Use Today

Ten production-ready GSAP ScrollTrigger patterns: fade reveals, parallax, text effects, SVG drawing, mask reveals, and flip animations. Copy-paste code for each.

GSAP ScrollTrigger tutorial showing multiple scroll animation patterns including parallax, reveals, and text effects

Estimated reading time: 12 minutes | Skill level: Beginner to Intermediate

ScrollTrigger is the reason GSAP stays relevant when every framework ships its own animation primitives. It's not just a scroll listener — it's a full choreography system. Scrub, pin, batch, snap, refresh — the API covers cases that would take hundreds of lines in raw JavaScript.

This post covers ten patterns you'll actually use. Not demos. Not proof-of-concept toys. Patterns that show up in client work, portfolio sites, and product pages regularly.

Each example includes the core code and a link to a pre-built version in Annnimate if you want the complete implementation with all variants.


Setup

Before anything works, you need ScrollTrigger registered:

import { gsap } from "gsap"; import { ScrollTrigger } from "gsap/ScrollTrigger"; gsap.registerPlugin(ScrollTrigger);

If you're using a CDN:

<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/ScrollTrigger.min.js"></script>

Register the plugin once, at app startup. Registering it inside components or functions that run multiple times causes issues.


1. Element Reveal on Scroll

The most common ScrollTrigger pattern. Elements fade and slide into view as you scroll past them.

gsap.from(".card", { scrollTrigger: { trigger: ".card", start: "top 85%", toggleActions: "play none none none" }, opacity: 0, y: 40, duration: 0.8, ease: "expo.out" });
For multiple elements, use
gsap.utils.toArray
and stagger:
gsap.from(gsap.utils.toArray(".card"), { scrollTrigger: { trigger: ".cards-grid", start: "top 80%", }, opacity: 0, y: 40, duration: 0.8, ease: "expo.out", stagger: 0.1 });
The
start: "top 85%"
means the animation fires when the top of the trigger element hits 85% down the viewport. Adjust this number to control how early or late the reveal happens.

For a production-ready version with six reveal variants (fade, slide, scale, clip path, blur fade, rotate), see the Element Reveal animation.


2. Text Reveal by Lines

Raw
opacity
fades on text feel lazy. Line-by-line reveals — where each line wipes in from below a mask — feel intentional. The technique requires wrapping each line in a container that clips the overflow, then animating the text up from
yPercent: 100
:
import { SplitText } from "gsap/SplitText"; gsap.registerPlugin(SplitText); const split = SplitText.create(".headline", { type: "lines", mask: "lines" }); gsap.from(split.lines, { scrollTrigger: { trigger: ".headline", start: "top 80%" }, yPercent: 100, opacity: 0, duration: 1, ease: "expo.out", stagger: 0.08 });
The
mask: "lines"
option in SplitText automatically wraps each line in a clipping container. Without it, you'd need to add wrapper divs manually.

For the complete implementation with scroll scrub mode and accessibility considerations, see Text Reveal.


3. Parallax Depth Effect

Parallax is the effect where background elements move slower than the page, creating an illusion of depth. Overused when done obviously, invisible when done subtly.

gsap.to(".background-layer", { scrollTrigger: { trigger: ".section", start: "top bottom", end: "bottom top", scrub: true }, y: -100 }); gsap.to(".foreground-element", { scrollTrigger: { trigger: ".section", start: "top bottom", end: "bottom top", scrub: true }, y: -200 });
scrub: true
ties the animation progress directly to scroll position.
scrub: 1
adds a 1-second lag so the animation trails slightly behind scroll — usually smoother-feeling. The key to parallax that doesn't feel cheap: keep the movement range small. Backgrounds at
-80px
to
-120px
over a full viewport height. Foreground elements at
-150px
to
-200px
. Anything beyond that and it starts to look like a bad 2013 website.

See the Parallax animation for a full implementation with configurable speed layers.


4. Scroll-Scrubbed Mask Reveal

Instead of fading in, content appears through an expanding shape — a circle, rectangle, or custom clip path — directly tied to scroll progress.

gsap.from(".image-container", { scrollTrigger: { trigger: ".reveal-section", start: "top center", end: "bottom center", scrub: 1 }, clipPath: "circle(0% at 50% 50%)", ease: "none" }); // End state gsap.to(".image-container", { clipPath: "circle(100% at 50% 50%)" });
For a
fromTo
version that scrubs through the full transition:
gsap.fromTo(".image-container", { clipPath: "circle(0% at 50% 50%)" }, { clipPath: "circle(75% at 50% 50%)", ease: "none", scrollTrigger: { trigger: ".reveal-section", start: "top center", end: "bottom top", scrub: 1 } } );

The Mask Reveal animation includes circle, oval, rectangle, blob, and custom clip path variants, plus image masks.


5. Pinned Section with Horizontal Scroll

Pin a section vertically while content scrolls horizontally. Common for feature walkthroughs and timeline layouts.

const container = document.querySelector(".horizontal-container"); const slides = gsap.utils.toArray(".slide"); gsap.to(slides, { xPercent: -100 * (slides.length - 1), ease: "none", scrollTrigger: { trigger: container, pin: true, scrub: 1, end: () => "+=" + container.offsetWidth } });
pin: true
locks the trigger element in place while the user scrolls.
end: () => "+=" + container.offsetWidth
dynamically calculates the scroll distance needed to traverse all slides. Using a function for
end
means it recalculates on resize.

One thing to watch: pinned sections add extra scroll height to the page. Make sure your layout accounts for this, especially when stacking multiple pinned sections.


6. SVG Path Drawing

Animate SVG strokes drawing themselves on scroll. Works with any path element — signatures, illustrations, UI decorations.

import { DrawSVGPlugin } from "gsap/DrawSVGPlugin"; gsap.registerPlugin(DrawSVGPlugin); gsap.from(".svg-path", { scrollTrigger: { trigger: ".svg-section", start: "top 70%", end: "bottom 30%", scrub: 1 }, drawSVG: "0%" });
DrawSVG animates the
stroke-dasharray
and
stroke-dashoffset
properties. Your SVG path needs a
stroke
and no
fill
, or the effect won't be visible.

For paths that draw from a specific point rather than the beginning:

gsap.fromTo(".svg-path", { drawSVG: "50% 50%" }, { drawSVG: "0% 100%", scrollTrigger: { /* ... */ } } );

This draws outward from the center simultaneously in both directions — useful for logo reveals and decorative dividers.

See SVG Draw Path for the complete implementation.


7. Background Color Transition Between Sections

Change background and text colors smoothly as the user scrolls from section to section. Useful for storytelling pages where each section has a distinct mood.

const sections = gsap.utils.toArray(".color-section"); sections.forEach((section) => { const bg = section.dataset.bg; const color = section.dataset.color; ScrollTrigger.create({ trigger: section, start: "top center", end: "bottom center", onEnter: () => gsap.to("body", { backgroundColor: bg, color: color, duration: 0.6 }), onEnterBack: () => gsap.to("body", { backgroundColor: bg, color: color, duration: 0.6 }) }); });

HTML:

<section class="color-section" data-bg="#0a0a0a" data-color="#fafafa">Dark section</section> <section class="color-section" data-bg="#fafafa" data-color="#0a0a0a">Light section</section>
The
onEnterBack
callback handles the reverse — when scrolling back up through a section. Without it, colors only update on downward scroll.

The Background Color animation handles multiple zones with smooth transitions and optional text color changes.


8. Velocity-Based Distortion

Read scroll velocity and distort elements based on how fast the user is scrolling. Fast scroll = more distortion. Slow scroll = elements return to normal.

ScrollTrigger.create({ onUpdate: (self) => { const velocity = self.getVelocity(); const clampedVelocity = gsap.utils.clamp(-1000, 1000, velocity); const skew = gsap.utils.mapRange(-1000, 1000, -10, 10, clampedVelocity); gsap.to(".distort-target", { skewY: skew, duration: 0.5, ease: "power3.out", overwrite: true }); } });
self.getVelocity()
returns scroll velocity in pixels per second.
gsap.utils.mapRange
converts that to a skew degree.
overwrite: true
prevents animations from stacking when the user scrolls fast.

For clip path-based velocity distortion where elements clip rather than skew, see Velocity Clip.


9. Folding Text Reveal

Characters fold in 3D space as they reveal. Each character rotates on X or Y axis from a folded state to flat, creating a paper-fold effect.

import { SplitText } from "gsap/SplitText"; gsap.registerPlugin(SplitText); const split = SplitText.create(".fold-text", { type: "chars" }); gsap.from(split.chars, { scrollTrigger: { trigger: ".fold-text", start: "top 80%" }, rotationX: 90, transformOrigin: "0% 50% -20px", opacity: 0, duration: 0.8, ease: "expo.out", stagger: 0.03 });
The
transformOrigin: "0% 50% -20px"
shifts the rotation point back in Z space, creating the folding illusion. Without the Z offset, characters rotate around their face rather than their edge.

Perspective on the parent element amplifies the 3D effect:

.fold-text { perspective: 400px; }

See Folding Text for the full implementation with configurable fold direction and stagger patterns.


10. GSAP Flip on Scroll

GSAP Flip records element positions before and after a state change, then animates between them. Combined with ScrollTrigger, elements can smoothly rearrange as you scroll.

import { Flip } from "gsap/Flip"; gsap.registerPlugin(Flip); ScrollTrigger.create({ trigger: ".flip-section", start: "top center", onEnter: () => { const state = Flip.getState(".flip-card"); // Change the layout (add class, move to different container, etc.) document.querySelector(".target-container").appendChild( document.querySelector(".flip-card") ); Flip.from(state, { duration: 0.8, ease: "expo.inOut" }); } });
Flip works by snapshotting computed positions and sizes before the DOM change, then playing the transition after. The key is calling
Flip.getState()
before any DOM manipulation.

For a scroll-scrubbed version where elements move between zones as you scroll, see Flip Zone. For staggered multi-element flip reveals, see Multi Flip.


ScrollTrigger Performance Tips

A few things that cause problems in production:

Refresh on resize. ScrollTrigger calculates positions once. If your layout shifts on resize, call
ScrollTrigger.refresh()
or use the built-in resize handling. Pinned sections in particular need this. Avoid animating width and height. These trigger layout recalculation. Use
scaleX
/
scaleY
instead.

Use

will-change: transform
sparingly. It promotes elements to their own compositor layer. Useful on heavily animated elements, wasteful on everything else.

invalidateOnRefresh: true
recalculates start/end values on ScrollTrigger refresh. Required for any calculation that depends on element size:

ScrollTrigger.create({ trigger: ".dynamic-section", end: () => "+=" + document.querySelector(".dynamic-section").offsetHeight, invalidateOnRefresh: true });
Batch for lists. If you're animating many elements (50+), use
ScrollTrigger.batch()
instead of creating individual ScrollTriggers:
ScrollTrigger.batch(".card", { onEnter: (elements) => { gsap.from(elements, { opacity: 0, y: 40, stagger: 0.1, duration: 0.8, ease: "expo.out" }); } });

What's Next

These ten patterns cover the majority of scroll animation work. The more complex implementations — scroll-linked 3D transformations, physics-based momentum, WebGL shader effects driven by scroll — build on the same ScrollTrigger foundation.

If you want pre-built, copy-paste versions of all the animations above plus 40+ others, Annnimate has them ready to drop into any project. HTML/CSS/JS and React formats, with live previews and customizable parameters.

Written by

Julian Fella

Julian Fella

Founder

Related reading

GSAP SplitText tutorial showing character and line text animations with scroll triggers
Tutorials·April 17, 2026

GSAP Text Animation: A Practical SplitText Guide (2026)

How to animate text with GSAP SplitText. Covers chars, words, and lines with scroll-triggered reveals, stagger, and mask effects. Copy-paste examples included.

Read article
Read article
GSAP ScrollTrigger tutorial showing scroll-based animation with trigger markers and 60fps performance
Tutorials·March 27, 2026

GSAP ScrollTrigger Tutorial: Animate on Scroll (2025)

Learn GSAP ScrollTrigger with step-by-step examples. Includes code snippets, performance tips, and ready-to-use scroll animations for your project.

Read article
Read article
GSAP vs Framer Motion vs React Spring comparison for React animation libraries in 2026
Comparisons·April 3, 2026

GSAP vs Framer Motion vs React Spring: Which Should You Use in 2026?

Comparing GSAP, Framer Motion, and React Spring for React animation. Bundle sizes, performance data, code examples, and honest recommendations.

Read article
Read article

On This Page

Stay ahead of the curve

Join 1000+ creators getting animation updates. Unsubscribe anytime.

Pages

  • Home
    Home
  • Animations
    Animations
  • Blog
    Blog
  • FAQ
    FAQ
  • Pricing
    Pricing
  • Platform
    Platform
  • Changelog
    Changelog
  • Roadmap
    Roadmap

Social

  • LinkedIn
    LinkedIn
  • Instagram
    Instagram
  • X / Twitter
    X / Twitter

Contact

  • Email me
    Email me

Legal

  • Privacy Policy
    Privacy Policy
  • Terms of Service
    Terms of Service
  • Cookie Policy
    Cookie Policy
  • Refund Policy
    Refund Policy

© 2026 Annnimate. All rights reserved.