Tutorial

Professional animation with GSAP

The animation library used by Awwwards winners, Apple, Google, and 11 million other sites. Fluent API, blazing fast, works on anything you can put a selector on.

Step 1

What GSAP is (and why it's famous)

GSAP (GreenSock Animation Platform) is a JavaScript library for animating anything — HTML, SVG, canvas, WebGL, React props, even arbitrary JS values. It's famous because it's the fastest, most reliable animation tool on the web, with an API that reads like plain English.

CSS animations vs. GSAP — when to reach for each

Use CSS when

  • Simple hover, focus, or state transitions
  • Animation runs once and doesn't need sequencing
  • You want zero JavaScript overhead
  • The animation is tied to user interaction (hover, focus)

Use GSAP when

  • You need to sequence multiple animations (timeline)
  • Animations depend on scroll position
  • You need to animate SVG morphs, paths, or 3D transforms smoothly
  • You want pause, reverse, restart controls
  • You're staggering lots of elements
  • Performance & consistency across browsers matters
Step 2

Installing GSAP — one line

The CDN is the easiest way to start. Paste one script tag, and gsap is available globally.

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

<script>
  gsap.to('.box', { x: 200, duration: 1 });
</script>
npm install gsap

// then in your JS
import gsap from 'gsap';

gsap.to('.box', { x: 200, duration: 1 });
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>

<script>
  gsap.registerPlugin(ScrollTrigger);
</script>
License note: GSAP core is 100% free for any project, including commercial. Some bonus plugins (SplitText, MorphSVG, DrawSVG) require a paid Club GreenSock membership, but 95% of what people need is free.
Demo 1

gsap.to() — the one method you'll use most

Click the button to animate the box. Click it again to watch it re-run. gsap.to() means "animate these elements to these values over this duration."

<div class="box"></div>

<button onclick="animate()">Run</button>
function animate() {
  gsap.to('.box', {
    x: 200,              // move 200px to the right
    rotation: 360,       // spin once
    scale: 1.4,          // grow 40%
    backgroundColor: '#ec4899',
    duration: 1,         // over 1 second
    ease: 'power2.out',  // smooth deceleration
    yoyo: true,          // come back
    repeat: 1            // once is enough
  });
}
Demo 2

Stagger — animate lists with one line

stagger tells GSAP to delay each element by a bit, giving you a cascading effect for free. This would be tedious in raw CSS.

gsap.to('.box', {
  y: 0,
  opacity: 1,
  duration: 0.6,
  ease: 'back.out(1.7)',
  stagger: 0.08           // each box starts 80ms after the previous
});

// Stagger supports more:
gsap.to('.grid-item', {
  scale: 1,
  stagger: {
    each: 0.05,
    from: 'center',       // 'start' | 'end' | 'center' | 'edges' | 'random'
    grid: [6, 6]          // treat as a 6×6 grid
  }
});
Demo 3

Timelines — choreograph multiple animations

A gsap.timeline() lets you sequence animations one after another, or overlap them, without calculating delays by hand. This is where GSAP really pulls ahead of raw CSS.

Done! 🎉
const tl = gsap.timeline();

tl.to('.box',  { x: 300, duration: 0.8, ease: 'power2.inOut' })
  .to('.box',  { rotation: 360, duration: 0.6 })
  .to('.box',  { y: -40, duration: 0.3, yoyo: true, repeat: 1 })
  .to('.box',  { x: 0, duration: 0.6, ease: 'power2.inOut' })
  .to('.text', { opacity: 1, y: -10, duration: 0.4 }, '-=0.2');

// The '-=0.2' at the end overlaps the last tween by 0.2s
// (start 0.2s before the previous animation ends)

// Timelines expose controls:
tl.pause();
tl.reverse();
tl.restart();
tl.progress(0.5);   // jump to 50%
Demo 4

Animating text, letter by letter

Split a string into characters, then animate each one with stagger. This is how every award-winning hero section works.

const target = document.getElementById('splitText');
const text = 'Hello, animation.';

// Wrap each character in its own span
target.innerHTML = text
  .split('')
  .map(c => c === ' '
    ? ' '
    : `<span class="char">${c}</span>`
  ).join('');

gsap.from('.char', {
  y: 40,
  opacity: 0,
  rotation: 12,
  duration: 0.6,
  ease: 'back.out(2)',
  stagger: 0.04
});
Demo 5

ScrollTrigger — animate as the user scrolls

ScrollTrigger is GSAP's most famous plugin. It fires animations tied to scroll position — fade in on enter, pin elements, progress bars, parallax. Scroll down to see it fire.

This box appears when you scroll to it

When 80% of the viewport passes this element, GSAP fades it in, rotates it, and scales it up. If you scroll back past it, it resets.

gsap.registerPlugin(ScrollTrigger);

gsap.from('.scroll-box', {
  scale: 0.2,
  rotation: -180,
  opacity: 0,
  duration: 1,
  ease: 'power3.out',
  scrollTrigger: {
    trigger: '.scroll-box',
    start: 'top 80%',         // fires when top of box hits 80% down viewport
    end:   'bottom 20%',
    toggleActions: 'play none none reverse'
    // actions for: onEnter | onLeave | onEnterBack | onLeaveBack
  }
});

gsap.from('.scroll-text', {
  x: 80,
  opacity: 0,
  duration: 1,
  scrollTrigger: { trigger: '.scroll-text', start: 'top 80%' }
});
Pro tip: Add scrub: true to make the animation scrub with the scrollbar, not just trigger once. That's how scroll-driven video-like storytelling works.
Step 3

Easing — the soul of an animation

The same animation with a different ease can feel completely different. GSAP ships with dozens of curves — here are the ones you'll actually use:

// Linear — straight line. Rarely looks natural.
ease: 'none'

// Power (most common) — accelerates or decelerates
ease: 'power1.out'       // subtle
ease: 'power2.out'       // moderate (default)
ease: 'power3.out'       // strong
ease: 'power4.out'       // dramatic

// Back — overshoots the target
ease: 'back.out(1.7)'    // classic "bounce past then settle"

// Elastic — wobbles around the target
ease: 'elastic.out(1, 0.3)'

// Bounce — physical ball bounce
ease: 'bounce.out'

// Custom — your own curve
ease: 'cubic-bezier(.17,.67,.83,.67)'

// Browse all at: greensock.com/ease-visualizer
Cheat Sheet

The whole GSAP core in 30 lines

// Animate TO these values
gsap.to('.box', { x: 200, duration: 1 });

// Animate FROM these values (to the CSS default)
gsap.from('.box', { y: -80, opacity: 0, duration: 0.6 });

// Animate FROM and TO (both ends specified)
gsap.fromTo('.box',
  { opacity: 0, y: 20 },
  { opacity: 1, y: 0, duration: 0.8 }
);

// Set values instantly (no tween)
gsap.set('.box', { scale: 1.2 });

// Kill all running animations on an element
gsap.killTweensOf('.box');

// Timeline — sequence animations
const tl = gsap.timeline({ repeat: -1, yoyo: true });
tl.to('.a', { x: 200 })
  .to('.b', { y: 100 }, '<')       // start with previous
  .to('.c', { rotation: 90 }, '+=0.5'); // 0.5s after previous ends

// Stagger across a group
gsap.to('.item', { opacity: 1, stagger: 0.05 });

// ScrollTrigger — tie to scroll
gsap.to('.hero', {
  y: 100,
  scrollTrigger: {
    trigger: '.hero',
    start: 'top top',
    end: 'bottom top',
    scrub: true
  }
});
Go deeper: gsap.com/docs · the cheat sheet: gsap.com/cheatsheet