Why reach for GSAP?
CSS handles simple transitions and keyframes well, and for a hover effect or a fade-in it is the right tool. The moment you need to coordinate several elements, tie animation to scroll, or control playback dynamically, CSS becomes awkward. That is where GSAP (GreenSock Animation Platform) shines.
GSAP gives you a small, expressive API for animating values over time, and a timeline model for sequencing them. It runs on requestAnimationFrame, animates efficiently, and its plugin ecosystem covers scrolling, dragging, morphing and more. For polished marketing sites, product tours and data storytelling, it is the standard.
Tweens
A tween changes one or more properties over a duration.
// tweens.js
import gsap from "gsap";
gsap.to(".box", { x: 200, duration: 1, ease: "power2.out" });
gsap.from(".title", { y: 40, opacity: 0, duration: 0.8 });
gsap.fromTo(".card", { scale: 0.8 }, { scale: 1, duration: 0.4 });
to animates to a value, from animates from a value to the current state, and fromTo specifies both. GSAP handles transforms (x, y, scale, rotation) natively, which are the properties that stay on the compositor and keep animation smooth.
Timelines
A timeline sequences tweens on a shared clock, with position parameters that make timing precise.
// timeline.js
const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
tl.from(".nav", { y: -20, opacity: 0 })
.from(".hero h1", { y: 40, opacity: 0 }, "-=0.3")
.from(".hero p", { y: 20, opacity: 0 }, "-=0.2")
.from(".hero button", { scale: 0.9, opacity: 0 }, "-=0.2");
The "-=0.3" means “start 0.3 seconds before the previous tween ends”, which overlaps steps for a natural flow. Timelines can be paused, reversed, sped up, seeked and labelled, which makes them far more controllable than a chain of setTimeout calls. They also make cleanup trivial: kill the timeline and every tween stops.
Stagger and easing
Stagger offsets the same animation across many targets, which is the key to the polished list and grid effects you see on modern sites.
// stagger.js
gsap.from(".card", {
y: 30,
opacity: 0,
duration: 0.5,
stagger: 0.08,
});
Easing controls the feel. power2.out starts fast and settles, back.out overshoots slightly, and elastic bounces. GSAP’s easing is one of its strengths and is hard to match with CSS alone.
ScrollTrigger
ScrollTrigger ties animation to scroll position, enabling the scroll-driven storytelling that defines many award-winning sites.
// scroll.js
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);
gsap.to(".panel", {
xPercent: -100,
ease: "none",
scrollTrigger: {
trigger: ".panels",
start: "top top",
end: "+=2000",
scrub: true,
pin: true,
},
});
scrub: true links the animation progress to the scrollbar, and pin: true holds the section in place while scrolling. ScrollTrigger also supports snap, toggles and callbacks, and handles resizing and refresh for you.
Performance
Motion should feel effortless, and that means animating the right properties.
- Animate transform and opacity, which the compositor can handle without layout.
- Avoid animating
width,height,top,leftormargin, which force layout every frame. - Animate fewer elements at once, and pause offscreen animations.
- Use
will-changesparingly on elements that are about to animate. - Kill timelines when a component unmounts to avoid leaks and wasted work.
See the Web Performance guide for how animation interacts with metrics like INP.
React and cleanup
The official useGSAP hook from @gsap/react scopes selectors to a container and cleans up automatically, which avoids the common pitfalls of animating React-managed DOM.
// Hero.jsx
import { useRef } from "react";
import { useGSAP } from "@gsap/react";
import gsap from "gsap";
export function Hero() {
const ref = useRef(null);
useGSAP(() => {
gsap.from(".title", { y: 40, opacity: 0 });
}, { scope: ref });
return <section ref={ref}><h1 className="title">Hello</h1></section>;
}
The hook handles StrictMode’s double invocation and reverts animations on unmount, so you do not leak tweens or leave elements in a half-animated state.
Accessibility
Always respect prefers-reduced-motion. For users with motion sensitivity, replace movement with a simple fade or skip the animation entirely.
// reduced.js
const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
gsap.from(".card", { y: 30, opacity: 0, stagger: 0.08 });
});
mm.add("(prefers-reduced-motion: reduce)", () => {
gsap.from(".card", { opacity: 0 });
});
This is a small amount of code that makes a real difference for a meaningful group of users.
Best practices
- Use timelines instead of chained timeouts for any sequence.
- Animate transform and opacity, not layout properties.
- Stagger lists for a polished, natural feel.
- Register plugins once and reuse them.
- Clean up timelines on unmount, especially in React.
- Respect prefers-reduced-motion.
- Pause or kill offscreen animations.
Common mistakes
- Animating layout properties and causing jank.
- Chaining
setTimeoutcalls that drift and are hard to cancel. - Leaving timelines running after a component unmounts.
- Overusing motion until the page feels busy.
- Ignoring reduced-motion preferences.
- Running heavy scroll animations on low-powered devices without testing.
Where to go next
GSAP turns animation from a chore into a design tool. Start with the CSS animations baseline so you know when you need more, then add GSAP for sequencing and scroll. Pair it with Three.js for 3D scenes and keep an eye on the metrics in the Web Performance guide. Then animate one hero section and feel the difference a well-timed sequence makes.