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.
<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:
gsap.to()— animate to a target stategsap.from()— animate from a starting state (the current CSS is the end)gsap.fromTo()— explicit start and end states
// 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.
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.
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.
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.
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,
},
});
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:
- MotionPath plugin — animate elements along an SVG path
- Flip plugin — smooth layout transitions (think card expansions)
- SplitText plugin — animate text at the character and word level
- Draggable — physics-based drag with bounds and snap points
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.