GSAP (GreenSock Animation Platform) is the JavaScript animation library most professional sites actually reach for once plain CSS transitions stop being enough — smooth cross-browser easing, animations you can sequence and reverse on command, and a scroll-triggering system (ScrollTrigger) that would otherwise take real effort to build by hand. It’s also, refreshingly, not hard to get started with: the entire library boils down to a handful of core ideas, and once you’ve animated your first element you already understand most of what real GSAP code looks like. This tutorial builds those ideas up one at a time, from moving a single box to a scroll-triggered reveal, using nothing but the free CDN build — no install, no build step, no account.
Every demo below is live and interactive right here in the article — click the buttons, hover the card, scroll the last one. Each embed also has a Fork & Edit button that opens the exact code, already running, in My Code — webdevpuneet’s free in-browser editor. Change a duration, swap an ease, and see what happens — that’s a much faster way to build real intuition for GSAP than reading about it.
Once this post clicks, the GSAP Playground is the natural next stop — a free, click-based, in-browser course that runs beginner to pro across 55 lessons in 18 chapters, covering everything from gsap.to() through timelines, stagger, ScrollTrigger, and the official plugins, each with a live editor and instant preview.
Why reach for GSAP instead of plain CSS?
CSS transitions and @keyframes genuinely handle a lot of simple cases fine, and this tutorial isn’t telling you to abandon them. GSAP earns its place once an animation needs to be controlled from JavaScript rather than just declared once and left alone: playing, pausing, reversing, or restarting on demand; chaining several animations into one precise sequence without a pile of animation-delay values; animating properties CSS can’t touch directly, like a number counting up or a value along an SVG path; or triggering smoothly off scroll position instead of just a class toggle. It also quietly papers over years of small cross-browser inconsistencies in how transforms and easing behave, so an animation that looks right in one browser looks right everywhere. Every demo below is deliberately simple, but each one demonstrates a real, load-bearing piece of how GSAP code is actually written.
1. gsap.to() — your first tween
The single most-used line in all of GSAP. It animates an element toward the values you give it, starting from wherever it already is right now.
The rule:
gsap.to('.box', {
x: 160,
rotation: 360,
backgroundColor: '#16a34a',
duration: 1.2,
ease: 'power2.out'
});
A “tween” (short for “in-betweening”) is GSAP’s core unit of animation: one call, one target, one set of end values, one duration. Note x and rotation rather than raw CSS transform strings — GSAP quietly handles combining transforms under the hood, which is genuinely fiddly to get right by hand, especially once translation and rotation need to animate at the same time. ease shapes the rate of change over that duration; without one specified, GSAP defaults to a gentle power1.out rather than the linear motion most people expect. gsap.to() can animate almost any numeric CSS property, plus a handful of GSAP-specific shorthand properties like x/y for transforms.
Best for: the vast majority of one-off animations — moving, resizing, fading, or recoloring something in response to code running. Tip: nothing here needs a build step or npm install — the single CDN script tag in this demo’s CDN panel is the entire dependency.
2. gsap.from() — animate from a state
The mirror image of gsap.to(): instead of animating toward values you give it, it animates from them back to the element’s real, current CSS state.
The rule:
gsap.from('.card', {
opacity: 0,
y: -40,
scale: 0.8,
duration: 0.8,
ease: 'back.out(1.7)'
});
This is the technique behind almost every “entrance animation” you’ve seen on a real website: the card’s actual layout position never changes at all — only the starting point of its animation does. GSAP briefly sets the element to the values you provided, then animates it forward to whatever its normal CSS already says, so you never have to hand-write the destination values yourself. That also means a gsap.from() tween is instantly reversible into a gsap.to() in your head: the numbers you’d write are simply the numbers you already wrote here.
Best for: entrance animations — a card, modal, or section fading and sliding into its normal resting position the moment it appears. Tip: because the destination is “whatever the CSS already says,” redesigning the element’s final look later needs zero changes to this tween — only the CSS.
3. gsap.fromTo() — set both ends explicitly
The most explicit of the three: you specify the starting values and the ending values yourself, so the animation looks identical no matter what state the element happened to be in beforehand.
The rule:
gsap.fromTo('.dot',
{ x: 0, opacity: 0.3, scale: 1 },
{ x: 260, opacity: 1, scale: 1.15, duration: 1, ease: 'power1.inOut' }
);
gsap.to() depends on wherever the element already is; gsap.from() depends on whatever its final CSS already says. gsap.fromTo() depends on neither — both objects are spelled out, so replaying the animation (as this demo does, via yoyo and repeat) always starts from the exact same place every time, which matters a lot once an animation needs to run more than once from a predictable state.
Best for: any animation that needs to replay identically — a toggle switch, a progress indicator, or anything triggered repeatedly by user interaction rather than played once on load. Tip: if an animation ever looks “off” the second time it plays but fine the first time, that’s usually a sign it should be a fromTo() instead of a to() or from().
4. Easing changes everything
Same distance, same duration, four completely different feelings. Easing is where GSAP animations stop looking like a spreadsheet and start looking like real motion.
The rule:
gsap.to('.dot', { x: 260, duration: 1.4, ease: 'power2.out' });
// also try: 'none', 'back.out(2)', 'bounce.out', 'elastic.out(1, 0.3)'
ease is a plain string naming one of GSAP’s built-in easing functions, and the differences are dramatic: none (linear) moves at a perfectly constant speed and reads as mechanical; a power family ease (power1 through power4, each with .in/.out/.inOut variants) accelerates or decelerates smoothly, which is what almost all natural-feeling UI motion actually uses; back overshoots past the target and springs back; bounce literally bounces like a dropped ball. Getting comfortable naming a handful of these by ear is one of the highest-value things to learn early — the exact same tween code reads as completely different quality of motion depending only on this one string.
Best for: tuning any animation’s personality without touching its duration, distance, or target values. Tip: power2.out or power3.out is a safe, professional-feeling default for most UI motion — reach for back or elastic deliberately for playful moments, not by default everywhere.
5. duration & delay
Two properties that are easy to mix up: duration controls how long a tween takes; delay controls how long it waits before starting at all.
The rule:
gsap.to('.box', { y: 0, opacity: 1, duration: 0.6, delay: 0.4 });
All three boxes in this demo take the same 0.6s duration to complete once they start — what delay changes is purely when that clock starts ticking, measured in seconds from the moment the tween is created (or, inside a timeline, from a point you control, as demo #6 shows next). This distinction matters most once several elements need to appear in a deliberate sequence rather than all at once: getting that sequence right by hand means carefully calculating a different delay for every single element, which is exactly the tedious math the next two techniques exist to eliminate.
Best for: a small, fixed number of elements that need to start at specific, deliberately chosen moments. Tip: once you’re hand-calculating more than two or three different delay values for similar elements, that’s the signal to reach for stagger (demo #7) instead.
6. gsap.timeline() — chaining tweens
A timeline is a container that plays multiple tweens in order automatically — no delay math required at all.
The rule:
const tl = gsap.timeline();
tl.to('.box', { x: 120, duration: 0.6 })
.to('.box', { y: 60, duration: 0.6 })
.to('.box', { rotation: 180, duration: 0.6 });
Chaining .to() calls onto a timeline (instead of calling gsap.to() directly, three separate times) means each step automatically starts exactly when the previous one finishes — add a sixth step, or reorder two of them, and nothing else needs recalculating. This is the single biggest reason real GSAP code reaches for timelines almost immediately: any animation with more than one meaningful step benefits from one, and a timeline itself can be paused, reversed, or replayed as a single unit, exactly like an individual tween can.
Best for: any multi-step animation — an intro sequence, a multi-part hover effect, a step-by-step onboarding animation. Tip: GSAP timelines support position parameters (like "-=0.2" as a second argument to .to()) for overlapping steps slightly instead of always waiting for a full, clean handoff — worth exploring once the basic chain above feels comfortable.
7. stagger — animating a group
One call, ten targets, one stagger value — the fix for the “hand-calculating delays” problem demo #5 raised.
The rule:
gsap.from('.col', {
scaleY: 0,
duration: 0.5,
stagger: 0.07
});
Pass a CSS selector matching multiple elements to any tween method, add a stagger value, and GSAP automatically offsets each target’s individual start time by that many seconds — the first bar starts immediately, the second 0.07s later, the third 0.07s after that, and so on, with zero loop or per-element math required. stagger also accepts an options object for more control (staggering from the center outward, or from a specific index), but the plain number shown here covers the large majority of real use cases: a list of cards, a grid of icons, or a row of chart bars all revealing themselves in a natural, cascading sequence instead of popping in simultaneously.
Best for: revealing a list, grid, or row of similar items one after another instead of all at once. Tip: a stagger between roughly 0.05 and 0.1 seconds reads as a natural cascade for most UI lists — much larger than that starts to feel slow and deliberate rather than snappy.
8. repeat & yoyo — looping
Two properties for animations that don’t just play once: repeat controls how many extra times a tween plays, and yoyo controls whether it reverses each time instead of snapping back to the start.
The rule:
gsap.to('.pulse', {
scale: 1.5,
duration: 0.6,
repeat: -1,
yoyo: true
});
repeat: -1 is GSAP’s convention for “loop forever” — a plain positive number instead (like repeat: 3) plays that many additional times and then stops. Without yoyo: true, each repeat snaps instantly back to the tween’s starting values before playing forward again, which usually reads as a visible jump; with it, the tween plays forward, then genuinely reverses back to where it started, then forward again, producing the smooth back-and-forth “breathing” pulse this demo shows. This combination is exactly how most looping attention-getters — a pulsing notification dot, a subtly breathing call-to-action button — are actually built.
Best for: pulsing indicators, breathing buttons, and any subtle looping attention cue. Tip: always give yourself a way to stop or kill a repeat: -1 tween (this demo’s Stop button calls .kill()) — an infinite loop with no way to end it is very easy to accidentally leave running in real code.
9. Animating on a real event
Everything above ran on a button click for the sake of a clean demo. This is how GSAP actually ships in real interfaces — tied to a genuine user interaction, not autoplay.
The rule:
card.addEventListener('mouseenter', function () {
gsap.to(card, { y: -8, duration: 0.3, ease: 'power2.out' });
});
card.addEventListener('mouseleave', function () {
gsap.to(card, { y: 0, duration: 0.3 });
});
There’s nothing GSAP-specific about the event listeners themselves — mouseenter and mouseleave are plain DOM events, exactly as they’d be used with any other JavaScript. The only GSAP-specific part is what happens inside each handler: a fresh gsap.to() call with the values for that state. Calling gsap.to() again mid-animation (say, if the cursor leaves before the hover animation finished) doesn’t cause a jarring jump — the new tween starts from the element’s current values, not the interrupted animation’s original starting point, and because it renders after the older tween it wins on every frame. GSAP 3 doesn’t kill the older tween by default (overwrite is false); add overwrite: 'auto' if you want the conflicting parts of the old tween removed outright, which is cleaner on busy hover effects.
Best for: hover states, focus states, click feedback — any animation that should respond to something the visitor actually does. Tip: this “continue from wherever it is” behavior is a big part of why GSAP hover effects feel more polished than naive manual class-toggling under fast mouse movement — and overwrite: 'auto' keeps it tidy when the cursor flicks in and out quickly.
10. ScrollTrigger — a first taste
The exact same gsap.from() from demo #2, just handed a scrollTrigger option instead of firing immediately or on click.
The rule:
gsap.registerPlugin(ScrollTrigger);
gsap.from('.reveal', {
opacity: 0,
y: 50,
duration: 0.8,
scrollTrigger: {
trigger: '.reveal',
start: 'top 80%'
}
});
ScrollTrigger is an official GSAP plugin (loaded from its own CDN script, separate from core GSAP) that needs one line of setup — gsap.registerPlugin(ScrollTrigger) — before it can be used. Once registered, any tween can take a scrollTrigger object instead of playing right away: trigger names the element to watch, and start: 'top 80%' means “fire when this element’s top edge reaches 80% down the viewport.” This barely scratches the surface of what ScrollTrigger can do — scroll-scrubbed animations, pinned sections, and full scroll-driven storytelling all build on this exact same foundation, and a whole separate post on this blog digs much deeper into it.
Best for: the simplest, most common scroll-triggered use case — an element fading and sliding in the moment it enters view. Tip: for a full deep dive into scroll-triggered animation — scrubbing, pinning, thresholds, and several more real patterns — look for “GSAP ScrollTrigger Tutorial: Animate Elements on Scroll” in the JavaScript category on this blog. For the same “is it in view yet” idea without any library, “How to Detect When an Element Enters the Viewport Without a Library” covers it from the browser API side.
Common pitfalls
- Reaching for
gsap.to()when the goal is really an entrance animation.gsap.to()needs you to know and hand-write the destination values;gsap.from()(demo #2) already knows them — they’re just the element’s existing CSS. - Hand-calculating a different
delayfor every item in a list. That’s exactly the tedious mathstagger(demo #7) exists to replace — one property instead of N carefully-computed numbers. - Chaining animations with manual
setTimeout()calls instead of a timeline. Agsap.timeline()(demo #6) sequences steps automatically and lets the whole sequence be paused, reversed, or replayed as one unit — a pile of timeouts can do none of that. - Starting a
repeat: -1tween with no way to stop it. An infinite loop needs a reference to the tween (or timeline) so.kill()or.pause()can actually be called on it later, exactly as demo #8’s Stop button does. - Forgetting
gsap.registerPlugin(ScrollTrigger)before using scrollTrigger. ScrollTrigger ships as a separate script from core GSAP and has to be explicitly registered once, or anyscrollTriggeroption on a tween is silently ignored.
Frequently asked questions
Do I need to install GSAP with npm to follow this tutorial?
No — every demo above loads GSAP from a CDN with a single <script> tag, visible in each demo’s CDN panel. npm installation matters once a project has a real build step (React, Vue, a bundler), but nothing here requires one.
Is GSAP free to use?
Yes. Since GSAP 3.13 (April 2025, after Webflow acquired GreenSock), the whole library — every plugin, ScrollTrigger included — is free for all use, commercial included, under the standard GSAP license. The old paid “Club GreenSock” tier for the bonus plugins is gone.
What’s the difference between gsap.to(), gsap.from(), and gsap.fromTo()?
gsap.to() animates from the element’s current state to values you specify. gsap.from() animates from values you specify back to the element’s current (real) CSS state. gsap.fromTo() specifies both ends explicitly, depending on neither the element’s current state nor its final CSS — see demos #1, #2, and #3 for a side-by-side comparison of exactly this.
Why does my animation snap back to normal instead of staying at its end state?
This usually means the animated CSS property was also set with a real stylesheet rule that keeps re-applying after the tween’s inline styles are considered, or an unrelated re-render reset the element’s markup. Double-check nothing else in the page is setting the same property, and confirm the tween isn’t accidentally being killed or overwritten by another tween targeting the same element right after it.
Can GSAP animate things CSS transitions can’t?
Yes — GSAP can animate along an SVG path, tween a plain JavaScript number (useful for a count-up effect), sequence dozens of steps precisely with a timeline, and hook into scroll position via ScrollTrigger, none of which plain CSS transitions or @keyframes can do on their own.
Every demo above ships its full HTML, CSS and JS in the HTML / CSS / JS tabs in its own top bar. The fastest way to actually lock this in is to click Fork & Edit on demo #6, add a fourth .to() step to the timeline, and watch it slot into the sequence with zero delay math of your own. Once you’ve got a version you like, save it to My Code and it’s yours to keep, tweak further, or drop straight into a real project.
Recommended next step: open the GSAP Playground and work through its early chapters on tweens and timelines — free, click-based lessons with a live editor, covering exactly the ground this post just introduced, one guided step at a time. For more tutorials like this one, browse the GSAP category on the blog.
Discover more from WebDevPuneet
Subscribe to get the latest posts sent to your email.




