Free5 demos

ScrollTrigger

Scroll-driven animations with precise control

Create stunning scroll-based animations with viewport detection, pinning, scrubbing, and progress tracking. The most popular GSAP plugin for scroll animations.

  • Viewport-based triggering
  • Scrub animations to scroll position
  • Pin elements during scroll
  • Horizontal scroll support
  • Snap points
  • Progress callbacks

Viewport Trigger

Animate elements as they enter and exit the viewport

Beginner

Scroll down to see boxes animate in

Box 1
Box 2
Box 3
Box 4
Box 5
ViewportTrigger.tsx
'use client';

import { useRef } from 'react';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

export function ViewportTrigger() {
  const containerRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    const boxes = gsap.utils.toArray<HTMLElement>('.box');

    boxes.forEach((box) => {
      gsap.from(box, {
        opacity: 0,
        y: 50,
        duration: 0.8,
        scrollTrigger: {
          trigger: box,
          start: 'top 80%',
          end: 'top 50%',
          toggleActions: 'play none none reverse',
        },
      });
    });
  }, { scope: containerRef });

  return (
    <div ref={containerRef} className="space-y-8">
      {[1, 2, 3].map((i) => (
        <div key={i} className="box p-8 bg-surface rounded-lg">
          Box {i}
        </div>
      ))}
    </div>
  );
}

Scrub Animation

Tie animation progress directly to scroll position

Beginner
Scroll Progress0%

Scroll to scrub the animation

A
ScrubAnimation.tsx
'use client';

import { useRef } from 'react';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

export function ScrubAnimation() {
  const containerRef = useRef<HTMLDivElement>(null);
  const progressRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    gsap.to(progressRef.current, {
      scaleX: 1,
      ease: 'none',
      scrollTrigger: {
        trigger: containerRef.current,
        start: 'top center',
        end: 'bottom center',
        scrub: 0.5,
      },
    });
  }, { scope: containerRef });

  return (
    <div ref={containerRef} className="h-[200vh] relative">
      <div
        ref={progressRef}
        className="fixed top-20 left-0 right-0 h-1 bg-accent origin-left scale-x-0"
      />
    </div>
  );
}

Pin Section

Pin elements in place while scrolling through content

Intermediate
Scroll down
This section will pin

Scroll to see the effect

End of scroll area
PinSection.tsx
'use client';

import { useRef } from 'react';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

export function PinSection() {
  const containerRef = useRef<HTMLDivElement>(null);
  const pinnedRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    ScrollTrigger.create({
      trigger: containerRef.current,
      start: 'top top',
      end: '+=500',
      pin: pinnedRef.current,
      pinSpacing: true,
    });
  }, { scope: containerRef });

  return (
    <div ref={containerRef}>
      <div ref={pinnedRef} className="h-screen flex items-center justify-center bg-surface">
        <h2>I'm pinned!</h2>
      </div>
      <div className="h-screen bg-background" />
    </div>
  );
}

Horizontal Scroll

Transform vertical scrolling into horizontal movement

Intermediate
Panel 1
Panel 2
Panel 3
Panel 4

Scroll vertically to move horizontally

HorizontalScroll.tsx
'use client';

import { useRef } from 'react';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

export function HorizontalScroll() {
  const containerRef = useRef<HTMLDivElement>(null);
  const trackRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    const track = trackRef.current;
    if (!track) return;

    gsap.to(track, {
      x: () => -(track.scrollWidth - window.innerWidth),
      ease: 'none',
      scrollTrigger: {
        trigger: containerRef.current,
        start: 'top top',
        end: () => '+=' + track.scrollWidth,
        scrub: 1,
        pin: true,
      },
    });
  }, { scope: containerRef });

  return (
    <div ref={containerRef} className="overflow-hidden">
      <div ref={trackRef} className="flex">
        {[1, 2, 3, 4].map((i) => (
          <div key={i} className="w-screen h-screen flex-shrink-0 flex items-center justify-center">
            Panel {i}
          </div>
        ))}
      </div>
    </div>
  );
}

Parallax Layers

Create depth with multi-layer parallax scrolling

Intermediate

Scroll to see parallax layers move at different speeds

0.3x
0.6x
1.2x
0.8x
0.2x (slowest)
ParallaxLayers.tsx
'use client';

import { useRef } from 'react';
import { useGSAP } from '@gsap/react';
import gsap from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

export function ParallaxLayers() {
  const containerRef = useRef<HTMLDivElement>(null);

  useGSAP(() => {
    const layers = gsap.utils.toArray<HTMLElement>('[data-speed]');

    layers.forEach((layer) => {
      const speed = parseFloat(layer.dataset.speed || '1');
      gsap.to(layer, {
        y: () => (1 - speed) * ScrollTrigger.maxScroll(window) * 0.5,
        ease: 'none',
        scrollTrigger: {
          start: 0,
          end: 'max',
          scrub: true,
        },
      });
    });
  }, { scope: containerRef });

  return (
    <div ref={containerRef} className="relative h-[200vh]">
      <div data-speed="0.5" className="absolute inset-0 bg-surface/20" />
      <div data-speed="0.8" className="absolute inset-0 flex items-center justify-center">
        <div className="w-32 h-32 bg-accent/50 rounded-lg" />
      </div>
      <div data-speed="1.2" className="absolute inset-0 flex items-end justify-center pb-20">
        <div className="w-16 h-16 bg-foreground/20 rounded-full" />
      </div>
    </div>
  );
}