Why GSAP?
This post is a summary of my personal experience while learning and using GSAP. Which allows you to animate anything with CSS easily.
For instance, here’s a demo from a project I built:
ScrollTrigger
A simple method to initiate animations is by activating them when an element enters the viewport.
Below is an example of a call-to-action section that fades into visibility:
useGSAP(() => {
gsap.fromTo(
".cta-content",
{ y: 10, opacity: 0, filter: "blur(4px)" },
{
y: 0,
opacity: 1,
filter: "blur(0px)",
duration: 0.7,
stagger: 0.06,
ease: "power3.out",
scrollTrigger: {
trigger: ".cta-container",
start: "top 80%",
toggleActions: "play reverse play reverse",
},
},
);
}); It’s important to comprehend a few things here.
fromTo()→ Specifies the animation’s beginning and ending states. In this instance, the content begins blurry, transparent, and a little lower.duration→ Controls how long the animation takes, whileeasecontrols how the movement accelerates and decelerates.trigger→ Indicates which element ScrollTrigger should observe.start: "top 80%"→ Indicate that the animation will begin when the trigger’s top reaches 80% of the viewport height.toggleActions→ Regulates what occurs when you enter, scroll upward, and then enter the trigger again from below and left
The stagger value introduces a small delay between the animations of multiple targets.
There’s nothing special needed for the section itself:
<section className="cta-container w-full py-12 mb-0 lg:mb-12 lg:py-20">
<div className="relative overflow-hidden">
<div className="cta-content max-w-7xl mx-auto px-4 text-center">
<h2 className="text-4xl md:text-5xl lg:text-6xl font-medium tracking-tight text-brand-brown mb-10">
Header
</h2>
<Button
size="lg"
className="rounded-full px-10 h-14 text-lg gap-2 group cursor-pointer hover:scale-105 transition-transform"
>
Click Me
</Button>
</div>
</div>
</section> Because the animation targets .cta-content, the heading and button move together as one group. A little movement can help direct attention toward important content.
Timelines
Animating one element is straightforward. Things get more interesting when you want several elements to appear in a specific order. That’s where gsap.timeline() becomes useful. A timeline lets you place multiple animations on a shared sequence and control how they overlap.
Here’s an example of a hero section animating into view:
useGSAP(() => {
const timeline = gsap.timeline();
timeline.fromTo(
".hero-card",
{ opacity: 0, scale: 0.5, filter: "blur(100px)" },
{
opacity: 1,
scale: 1,
filter: "blur(0px)",
duration: 0.5,
ease: "circ.inOut",
},
);
timeline.to(
".hero-tagline",
{ y: 0, opacity: 1 },
"<+=0.3",
);
timeline.fromTo(
".hero-button",
{ y: 5, opacity: 0 },
{
y: 0,
opacity: 1,
pointerEvents: "auto",
stagger: 0.15,
ease: "power3.inOut",
},
"<+=0.5",
);
}); The first animation brings in the hero card. After that, the tagline appears, followed by the buttons. GSAP lets you position an animation relative to other animations in the timeline. For example, "<" refers to the start of the most recently added animation, while "<+=0.3" starts 0.3 seconds after that animation’s start.
This means animations don’t have to wait for the previous one to finish. They can overlap, creating a more fluid sequence.
SplitText
GSAP’s SplitText plugin makes it possible to split a text element into smaller targets that you can animate separately.
For example:
const splitHeader = new SplitText("#text-header", {
type: "chars",
mask: "lines",
smartWrap: true,
}); This splits the heading into individual characters. The line mask helps hide characters as they move into place, while smartWrap helps preserve sensible line wrapping when the layout changes.
Once the text has been split, those characters can be added to a timeline just like any other element:
timeline.fromTo(
splitHeader.chars,
{ opacity: 0, y: 20, scale: 0.95 },
{
opacity: 1,
y: 0,
scale: 1,
duration: 0.5,
stagger: 0.04,
},
); Add a stagger to make each letter appear one after another rather than all at once.
The result is a much more expressive heading without having to wrap every character in a separate element yourself.
SplitText is particularly useful for large headings and short phrases where the animation can become part of the design.