Back to Blog
Back to Blog
Tutorials·GSAP·November 12, 2025·9 min read

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.

GSAP ScrollTrigger tutorial showing scroll-based animation with trigger markers and 60fps performance

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

I've been building scroll animations for years, and I can't count how many times I've seen janky, stuttering scroll effects that ruin an otherwise beautiful site.

The culprit? Usually poorly implemented scroll listeners or awkward Intersection Observer setups.

In this tutorial, I'll show you exactly how to create smooth, performant scroll animations using GSAP ScrollTrigger. You'll learn:

  • How ScrollTrigger works and why it's better than alternatives
  • Setting up your first scroll animation in under 5 minutes
  • Creating parallax effects that run at 60fps
  • Performance optimization tricks I use in production
  • Troubleshooting common issues

By the end, you'll be able to build scroll animations that feel butter-smooth. Let's dive in.

What Is GSAP ScrollTrigger?

GSAP ScrollTrigger is a plugin that creates scroll-based animations by triggering GSAP tweens when elements enter or leave the viewport. It handles scroll events efficiently, works across all major browsers, and gives you precise control over animation timing.

Why ScrollTrigger Over Intersection Observer?

I've used both extensively. ScrollTrigger wins because:

  • Precision: You can trigger animations at exact scroll positions (not just visibility)
  • Scrubbing: Link animation progress directly to scroll position
  • Performance: Built-in throttling and optimization
  • Features: Pin elements, horizontal scrolling, batch animations out of the box

Real-world use cases:

  • Fade in content as users scroll
  • Parallax background effects
  • Pin navigation while scrolling
  • Animate progress bars based on page scroll
  • Reveal text character by character
GSAP ScrollTrigger markers showing trigger points

Prerequisites and Setup

Before we start, you should have:

  • Basic JavaScript knowledge (variables, functions)
  • Understanding of CSS positioning
  • A code editor and browser

That's it. You don't need to be a GSAP expert.

Installing GSAP and ScrollTrigger

The fastest way is via CDN (great for learning). For production, use npm.

Option 1: CDN (Quick Start)

<!-- Add before closing </body> tag --> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>

Option 2: NPM (Production)

npm install gsap

Then import in your JavaScript:

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

Project Structure

Here's the basic HTML structure we'll use:

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ScrollTrigger Demo</title> <style> body { margin: 0; font-family: system-ui, sans-serif; background: #0a0a0a; color: #fafafa; } section { height: 100vh; display: flex; align-items: center; justify-content: center; font-size: 3rem; font-weight: 600; } .box { width: 200px; height: 200px; background: #a4fb81; border-radius: 6px; } </style> </head> <body> <section> <h1>Scroll Down</h1> </section> <section> <div class="box"></div> </section> <!-- Scripts here --> </body> </html>

Your First Scroll Animation

Let's create a simple fade-in effect. When the
.box
enters the viewport, it fades in from 0 to 1 opacity.
// Select your element const box = document.querySelector(".box"); // Create the animation gsap.fromTo( box, { // Starting state opacity: 0, y: 100, }, { // Ending state opacity: 1, y: 0, duration: 1, ease: "power4.out", // ScrollTrigger configuration scrollTrigger: { trigger: box, // Element that triggers the animation start: "top 80%", // Animation starts when top of box hits 80% of viewport end: "bottom 20%", // Animation ends when bottom of box hits 20% of viewport toggleActions: "play none none reverse", // Play on enter, reverse on leave }, } );

Breaking it down:

  • opacity: 0 → 1
    - Fades in the element
  • y: 100 → 0
    - Moves up 100px as it fades in
  • duration: 1
    - Takes 1 second to complete
  • ease: "power4.out"
    - Smooth, natural easing (my go-to)
  • trigger: box
    - The animation starts when THIS element enters viewport
  • start: "top 80%"
    - Trigger when top of box reaches 80% down the viewport
  • toggleActions
    - What happens on enter/leave/enter-back/leave-back

Result: Scroll down and watch the box fade in smoothly.

GSAP ScrollTrigger fade-in animation demo

Want this animation ready to use? Check out our Fade In on Scroll animation in Annnimate.

Understanding ScrollTrigger Properties

ScrollTrigger has several key properties. Let's break down the most important ones.

The trigger Property

This is the element that activates the animation. It doesn't have to be the element you're animating.

scrollTrigger: { trigger: ".section", // Watch this element // Animate something else }

Start and End Positions

The
start
and
end
properties use this format:
"[trigger position] [viewport position]"

Examples:

start: "top top"; // Trigger's top hits viewport's top start: "top center"; // Trigger's top hits viewport's center start: "top 80%"; // Trigger's top hits 80% down viewport start: "bottom bottom"; // Trigger's bottom hits viewport's bottom

Scrub vs Toggle

Toggle (default): Animation plays once when triggered

scrollTrigger: { trigger: box, start: "top 80%", // Animation plays, then stops }

Scrub: Animation progress tied to scroll position

scrollTrigger: { trigger: box, start: "top bottom", end: "bottom top", scrub: true // Animation follows scroll }
With
scrub: true
, the animation rewinds as you scroll up. Use
scrub: 1
(or any number) for a slight delay, which feels smoother.

Adding Parallax Effects

Parallax is when background elements move slower than foreground elements, creating depth. It's easier than you think with ScrollTrigger.

Basic Parallax Setup

// Parallax background gsap.to(".parallax-bg", { y: -200, // Move up 200px ease: "none", // Linear movement for parallax scrollTrigger: { trigger: ".parallax-section", start: "top bottom", end: "bottom top", scrub: 1, // Smooth lag effect }, }); // Faster moving foreground gsap.to(".parallax-fg", { y: 100, // Move down 100px ease: "none", scrollTrigger: { trigger: ".parallax-section", start: "top bottom", end: "bottom top", scrub: 1, }, });

How it works:

  • Background moves in opposite direction (-200px up)
  • Foreground moves less (100px down)
  • Different speeds create the depth illusion
  • scrub: 1
    links animation to scroll with slight delay
Pro tip: Use
ease: "none"
for parallax. Other easing curves feel weird when tied to scroll. GSAP parallax scroll effect demonstration

Want production-ready parallax? Grab our Parallax Scroll animation from Annnimate with all the optimization built-in.

Performance Optimization

Scroll animations can tank performance if you're not careful. Here's how to keep them buttery smooth.

1. Use GPU Acceleration

Always animate
x
,
y
,
scale
, and
rotation
instead of
left
,
top
,
width
, or
height
.
// ❌ BAD - Triggers layout recalculation gsap.to(box, { left: "100px", top: "50px", }); // ✅ GOOD - GPU accelerated gsap.to(box, { x: 100, y: 50, force3D: true, // Force GPU acceleration });

2. Optimize Scrub Values

Lower scrub values = smoother but more CPU intensive.

scrub: 0.5; // Very smooth, higher CPU load scrub: 1; // Balanced (recommended) scrub: 2; // More lag, lower CPU load
I use
scrub: 1
for most projects. It's the sweet spot.

3. Batch Similar Animations

Instead of creating separate ScrollTriggers for each element:

// ❌ BAD - Creates 50 ScrollTriggers document.querySelectorAll(".card").forEach((card) => { gsap.from(card, { opacity: 0, scrollTrigger: { trigger: card }, }); }); // ✅ GOOD - One ScrollTrigger for all ScrollTrigger.batch(".card", { onEnter: (batch) => gsap.from(batch, { opacity: 0, y: 60, stagger: 0.15, }), });

Testing Performance: Open Chrome DevTools → Performance tab → Record while scrolling. You should see 60fps frame rate (green line) with no long tasks (yellow/red bars).

Advanced Techniques

Once you've mastered the basics, try these:

Pinning Elements

Pin an element while other content scrolls past:

ScrollTrigger.create({ trigger: ".pin-section", pin: true, // Pin this element start: "top top", end: "+=500", // Pin for 500px of scrolling });

Great for sticky navigation, side-by-side reveal content, and scroll-based storytelling.

Horizontal Scrolling

Create horizontal scroll sections:

const sections = gsap.utils.toArray(".horizontal-section"); gsap.to(sections, { xPercent: -100 * (sections.length - 1), ease: "none", scrollTrigger: { trigger: ".horizontal-container", pin: true, scrub: 1, end: () => "+=" + document.querySelector(".horizontal-container").offsetWidth, }, });

Want more advanced examples? Check out our collection of scroll animations in Annnimate.

Troubleshooting Common Issues

Problem: Animation Not Triggering

Symptoms: Nothing happens when you scroll

Solutions:

  • Check if GSAP and ScrollTrigger are loaded
  • Add
    markers: true
    to see trigger points
  • Verify your
    start
    and
    end
    positions
  • Make sure the trigger element exists in the DOM

Problem: Stuttering/Lag

Symptoms: Animation feels janky, dropped frames

Solutions:

  • Animate
    x/y
    instead of
    left/top
  • Add
    force3D: true
  • Reduce scrub intensity (
    scrub: 2
    instead of
    scrub: 0.5
    )
  • Avoid animating expensive properties (filter, box-shadow)
  • Use
    ScrollTrigger.batch()
    for many elements

Problem: Mobile Not Working

Symptoms: Works on desktop, breaks on mobile

Solution: Use
matchMedia
for responsive breakpoints:
ScrollTrigger.matchMedia({ // Desktop "(min-width: 768px)": function () { gsap.to(box, { x: 500, scrollTrigger: { trigger: box }, }); }, // Mobile "(max-width: 767px)": function () { gsap.to(box, { x: 200, // Less movement on mobile scrollTrigger: { trigger: box }, }); }, });

Key Takeaways

Here's what we covered:

  • ScrollTrigger creates scroll-based animations with precise control and great performance
  • Start with basics:
    trigger
    ,
    start
    ,
    end
    , and
    toggleActions
    are all you need
  • Scrub for parallax: Link animation progress to scroll position
  • Optimize for 60fps: Use x/y properties, force3D, and batch animations
  • Debug with markers: Add markers: true to see exactly when animations trigger

Want to skip the setup entirely? I've built 40+ production-ready scroll animations in Annnimate. Each one is optimized, tested, and ready to copy-paste into your project. Explore the collection.

What's Next?

Now that you've mastered ScrollTrigger basics, try:

  • Horizontal scroll sections
  • Text reveal with SplitText
  • Scroll-based SVG drawing

Questions? Found this helpful? Let me know on Twitter or join our community.

Happy scrolling! 🚀

Written by

Julian Fella

Julian Fella

Founder

On This Page

Stay ahead of the curve

Join 1000+ creators getting animation updates. Unsubscribe anytime.

Pages

  • Home
    Home
  • Collection
    Collection
  • 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.