Visual & Creative

Animating with GSAP: A Live Demo

← Back to blog

Why GSAP?

CSS animations are great for simple transitions — but the moment you want sequencing, scroll-based effects, or precise easing control, you hit a wall. GSAP (GreenSock Animation Platform) fills that gap. It's the animation library used in more than 11 million websites, including major studios and interactive news projects.

In this post I'll walk through the four core concepts with live demos you can click. Every demo shows the code that produces it, so you can follow along.

📝 Setup: Add GSAP via CDN — no bundler required.
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<!-- For scroll-based animations, also add: -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>

Tweens — the basic unit

A tween is a single animation from one state to another. GSAP gives you three methods:

Demo 1 — Basic Tween
// Animate the ball 200px to the right over 0.8s
gsap.to('#d1-ball', {
  x: 200,
  duration: 0.8,
  ease: 'power2.out',
});

Notice the ease property. GSAP ships with dozens of easing functions. power2.out starts fast and slows to a stop — the most natural feel for most UI animations. Try bounce.out, elastic.out(1, 0.5), or back.out(1.7) for very different characters.

💡 Tip: GSAP automatically handles the CSS transform — you write x instead of translateX, and it composites correctly. No matrix math required.

Timelines — sequencing made easy

A timeline lets you chain multiple tweens together and control them as a single unit. This is where GSAP becomes indispensable.

Demo 2 — Timeline
const tl = gsap.timeline({ defaults: { ease: 'power2.inOut' } });

tl.to('#d2-square', { scale: 1.4, duration: 0.4 })
  .to('#d2-square', { rotation: 180, duration: 0.5 })
  .to('#d2-square', { borderRadius: '50%', background: '#7c6afa', duration: 0.4 })
  .to('#d2-square', { scale: 1, rotation: 360, duration: 0.6, ease: 'back.out(1.7)' })
  .to('#d2-square', { borderRadius: '8px', background: '#00d4aa', duration: 0.3 });

The defaults object at the top applies to every tween in the timeline, so you don't repeat yourself. Each .to() call runs after the previous one finishes by default. Add a position parameter (a number or label) to overlap them.

Stagger — animating collections

One of the most visually satisfying effects in UI animation is a stagger: elements entering one after another with a small delay. GSAP makes this trivial.

Demo 3 — Stagger
gsap.to('.stagger-dot', {
  opacity: 1,
  y: 0,
  duration: 0.5,
  stagger: 0.07,       // 70ms delay between each element
  ease: 'back.out(2)',
});

The stagger value can be a number (uniform delay) or an object with from, grid, and amount properties for more complex patterns. Try stagger: { from: 'center', amount: 0.5 } for a ripple effect from the middle outward.

ScrollTrigger — animate on scroll

ScrollTrigger is GSAP's scroll plugin — it ties animation playback to the scroll position. The most basic use case is triggering an animation when an element enters the viewport.

Demo 4 — ScrollTrigger (simulated)

scroll fill

scrub: true

gsap.registerPlugin(ScrollTrigger);

gsap.to('.progress-bar', {
  width: '100%',          // animate to full width
  ease: 'none',           // linear — no easing for scrubbed animations
  scrollTrigger: {
    trigger: 'body',
    start: 'top top',     // when top of body hits top of viewport
    end: 'bottom bottom', // when bottom of body hits bottom of viewport
    scrub: true,          // tie animation progress to scroll position
  },
});

The scrub: true option is the key detail. It decouples animation playback from the timeline and instead maps it directly to scroll progress — so the animation plays forward as you scroll down, and in reverse as you scroll up. Set scrub: 0.5 for a slight lag that smooths out jerky scrolling.

The reading progress bar at the top of this page is built exactly this way — except using a vanilla scroll event listener rather than ScrollTrigger, since it tracks total page progress rather than a specific element.

Putting it together

A realistic page animation combining all four concepts might look like this:

gsap.registerPlugin(ScrollTrigger);

// 1. Hero stagger on load
gsap.from('.hero > *', {
  opacity: 0,
  y: 30,
  duration: 0.7,
  stagger: 0.12,
  ease: 'power2.out',
  delay: 0.2,
});

// 2. Cards animate in as they scroll into view
gsap.to('.post-card', {
  opacity: 1,
  y: 0,
  duration: 0.6,
  stagger: 0.09,
  ease: 'power2.out',
  scrollTrigger: {
    trigger: '.post-grid',
    start: 'top 88%',
  },
});

// 3. Post hero parallax
gsap.to('.post-hero-bg', {
  yPercent: 25,
  ease: 'none',
  scrollTrigger: {
    trigger: '.post-hero',
    start: 'top top',
    end: 'bottom top',
    scrub: true,
  },
});
💡 Performance tip: Stick to transform and opacity for animated properties — they run on the GPU compositor and don't trigger layout or paint. Avoid animating width, height, top, left, or padding unless you have no choice.

Where to go next

These four concepts — tweens, timelines, stagger, and ScrollTrigger — cover probably 95% of production animation work. The GSAP docs are excellent for going deeper. A few places worth exploring:

Start with something small — one card reveal, one hero entrance. Get it feeling exactly right. That attention to detail is what separates good interfaces from great ones.