Why reach for GSAP?
CSS animation is great for simple, self-contained motion. But when you need to sequence many steps, control playback (play, pause, reverse, scrub), animate any numeric property, or tie motion to scroll, hand-written CSS and JavaScript get painful fast. GSAP makes all of it short and reliable.
| Use CSS when… | Use GSAP when… |
|---|---|
| A hover, a single fade, a loading spinner | A multi-step sequence with precise timing |
| Motion that never needs to be controlled by JS | You need play / pause / reverse / scrub control |
| One element, one transition | Dozens of elements with staggered offsets |
| — | Scroll-driven animation (ScrollTrigger) |
Loading GSAP
The fastest start is the CDN: one script tag gives you the global gsap object. Plugins like ScrollTrigger are separate files you register once.
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script> <script src="…/ScrollTrigger.min.js"></script> <!-- optional plugin --> <script> gsap.registerPlugin(ScrollTrigger); register any plugins you load gsap.to('.box', { x: 200, duration: 1 }); </script>
npm install gsap and import it instead.)gsap.to() — the method you'll use most
gsap.to(target, vars) animates a target to the values you give. First argument: what to animate (a selector or element). Second: an object of end values plus options like duration and ease.
gsap.to('#box', { x: 240, move 240px right (uses transform) rotation: 360, spin once duration: 1, ease: 'power2.out' });
x/y, not left/top — under the hood these become GPU-friendly transforms, so motion stays smooth. rotation, scale, and opacity work the same way.from() & fromTo() — entrances
gsap.from() animates from the values you give to the element's current (resting) state — perfect for entrances. fromTo() lets you specify both ends explicitly.
gsap.from('.item', { y: 40, start 40px lower… opacity: 0, …and invisible, then settle to normal duration: 0.6 });
from() is the secret to clean entrance animations: you build the page in its final layout, then tell GSAP where elements should animate from. No need to set a starting style yourself.The vars object
That second argument — the “vars” object — holds both the properties to animate and the special options that control the tween. The common ones:
| Key | Does |
|---|---|
| x, y | Move (transform-based, in px). |
| rotation, scale | Spin and resize. |
| opacity | Fade in/out (0–1). |
| duration | Seconds the tween lasts. |
| delay | Seconds to wait before starting. |
| ease | The easing curve (Step 6). |
| repeat / yoyo | repeat: -1 loops forever; yoyo: true reverses each repeat. |
| stagger | Offset between multiple targets (Step 7). |
| onComplete | A function to run when it finishes. |
backgroundColor, borderRadius, width, strokeDashoffset… GSAP figures out how to interpolate it.Easing gives motion character
GSAP's eases go far beyond CSS. power2.out decelerates smoothly; back.out overshoots; elastic springs; bounce drops. Hit Run — same distance, same duration, very different feel:
power1–4.out covers most UI needs (higher = more dramatic deceleration). Save elastic and bounce for playful accents — they're attention-grabbing by design.Stagger: many elements, one tween
Add stagger and one call animates a whole group with a time offset between each — the cascading reveal you see everywhere. Replay this grid:
gsap.from('.dot', { scale: 0, opacity: 0, duration: 0.4, stagger: 0.04 40ms between each dot });
stagger can also be an object — stagger: { each: .05, from: 'center' } — to ripple out from the center, edges, or a grid position.Timelines: sequencing
A timeline chains tweens so they run in order — no manual delay math. Tweens added to a timeline play one after another by default, and you can overlap them with position offsets. The whole timeline is one controllable object.
const tl = gsap.timeline(); tl.from('.box1', { y: -40, opacity: 0 }) .from('.box2', { y: -40, opacity: 0 }) runs after box1 .from('.box3', { y: -40, opacity: 0 }, '-=0.2'); overlaps by .2s
tl.pause(), tl.reverse(), tl.timeScale(2) (double speed), or scrub it with a slider — control you simply can't get from CSS keyframes.ScrollTrigger: animate on scroll
The most popular GSAP plugin. ScrollTrigger ties any tween to the scroll position — play when an element enters the viewport, or scrub an animation as you scroll. The box below animates in the moment it scrolls into view:
(Scroll this section into view, or reload near it, to trigger it.)
gsap.registerPlugin(ScrollTrigger); gsap.from('.reveal', { y: 60, opacity: 0, duration: 0.8, scrollTrigger: { trigger: '.reveal', start: 'top 85%' when the top hits 85% down the screen } });
scrub: true to tie progress directly to the scrollbar (the animation rewinds as you scroll up), or pin: true to lock an element in place while a sequence plays — the foundation of modern scroll-telling sites.GSAP checklist
- Loaded GSAP (CDN or npm); plugins
registerPlugin()'d -
gsap.to()for target → values;from()for entrances - Animate
x/y/rotation/scale/opacity(transform-based, smooth) - A real ease chosen (
power2.outfor most UI) -
staggerfor groups; a timeline for sequences - Scroll effects via ScrollTrigger; motion kept purposeful & accessible