← all posts
2026-08-20·12 min readJavaScriptAnimationGSAP

How to Trigger CSS Animations with JavaScript (and When to Use GSAP)

The reliable ways to start a CSS animation from JavaScript, from toggling a class to the Web Animations API, plus when it's time to reach for GSAP and scroll triggers.

S
Saurabh Bhayana
Web developer & SEO specialist
// KEY TAKEAWAYS
  • The simplest way to trigger a CSS animation with JavaScript is to add or toggle a class with classList.add().
  • To restart an animation, remove the class, force a reflow with void element.offsetWidth, then add it back.
  • IntersectionObserver is the modern, performant way to trigger animations when an element scrolls into view.
  • The Web Animations API (element.animate()) lets you define and control animations entirely in JavaScript.
  • Use GSAP when you need timelines, scroll-scrubbing, or physics; use plain CSS + a class for simple triggers.

CSS animations are great, but they often need a nudge from JavaScript: start when a button is clicked, play when an element scrolls into view, or replay on demand. This guide covers every reliable way to trigger a CSS animation with JavaScript, from the one-liner everyone should know to the modern scroll-based approach, and when to graduate to GSAP.

1. The simplest trigger: toggle a class

The most common way to trigger a CSS animation with JavaScript is to define the animation in a CSS class, then add that class in JavaScript. The animation runs the moment the class lands on the element.

css
@keyframes pop {
  from { transform: scale(.8); opacity: 0; }
  to   { transform: scale(1);  opacity: 1; }
}
.is-animating { animation: pop .4s ease-out both; }
js
const el = document.querySelector(".card");
button.addEventListener("click", () => {
  el.classList.add("is-animating");
});

This keeps the animation itself in CSS (where it belongs) and uses JavaScript only as the trigger. It is the right default for clicks, toggles and state changes.

2. Restarting an animation on demand

A classic gotcha: adding a class that's already there does nothing. To replay an animation, you must remove the class, force the browser to acknowledge the change (a reflow), then add it back. This one-liner does the reflow:

js
function replay(el, cls) {
  el.classList.remove(cls);
  void el.offsetWidth;   // force reflow so the browser resets the animation
  el.classList.add(cls);
}
Tip

The void el.offsetWidth line looks strange but is doing real work: reading offsetWidth forces the browser to flush pending style changes, so removing and re-adding the class actually restarts the animation instead of being batched away.

3. Trigger on scroll with IntersectionObserver

How do you trigger a CSS animation on scroll, when an element enters the viewport? The modern, performant answer is IntersectionObserver. It watches elements and fires a callback when they become visible, without the jank of listening to every scroll event.

js
const observer = new IntersectionObserver((entries) => {
  for (const entry of entries) {
    if (entry.isIntersecting) {
      entry.target.classList.add("in-view");
      observer.unobserve(entry.target); // animate once
    }
  }
}, { threshold: 0.2 });

document.querySelectorAll(".reveal").forEach((el) => observer.observe(el));
css
.reveal { opacity: 0; transform: translateY(24px); transition: .6s ease; }
.reveal.in-view { opacity: 1; transform: translateY(0); }

This reveals elements as they scroll into view and stops observing them afterward, so it stays cheap. It's the pattern behind most 'fade up on scroll' effects you see.

4. The Web Animations API: animate in JavaScript

Sometimes you want the animation defined in JavaScript, not CSS, so you can control it, pause it, reverse it or await it. The Web Animations API (element.animate()) does exactly that, natively, no library needed.

js
const anim = el.animate(
  [
    { transform: "translateY(20px)", opacity: 0 },
    { transform: "translateY(0)",    opacity: 1 },
  ],
  { duration: 400, easing: "ease-out", fill: "forwards" }
);

anim.onfinish = () => console.log("done");
// anim.pause(); anim.reverse(); anim.play();

It returns an Animation object you can control programmatically. Great for one-off, JS-driven motion where you need fine control but don't want a dependency.

CSS animation vs GSAP: when to upgrade

You can do a lot with CSS plus a class toggle. But there's a point where hand-rolling gets painful, and that's where GSAP (GreenSock) earns its place. Here's the honest comparison:

Use plain CSS + JS when...Use GSAP when...
Simple hover, toggle or revealYou need a timeline that sequences many steps
One or two elementsYou need scroll-scrubbing (animation tied to scroll position)
No dependency wantedYou want physics, easing presets, or morphing
Basic in-view revealYou're staggering dozens of elements with precise control

GSAP's ScrollTrigger, in particular, makes scroll-linked animation trivial compared to wiring it by hand. A basic GSAP reveal looks like this:

js
import gsap from "gsap";
import { ScrollTrigger } from "gsap/ScrollTrigger";
gsap.registerPlugin(ScrollTrigger);

gsap.from(".reveal", {
  y: 40, opacity: 0, duration: .8, stagger: .1,
  scrollTrigger: { trigger: ".reveal", start: "top 85%" },
});
Rule of thumb: reach for GSAP when you catch yourself building a mini animation engine by hand. Until then, a CSS class and a click listener is lighter and perfectly good.

The short version

To trigger a CSS animation with JavaScript: toggle a class for clicks and state, use void offsetWidth to restart it, and IntersectionObserver to fire it on scroll. Use the Web Animations API when you need JS control without a library, and GSAP when you need timelines, scroll-scrubbing or physics. Match the tool to the job and your animations stay both smooth and maintainable.

The animated components on this site use exactly these techniques. You can copy them from the components library.

Frequently asked questions

How do I trigger a CSS animation with JavaScript?+

Define the animation in a CSS class using @keyframes, then add that class in JavaScript with element.classList.add('class-name'). The animation runs as soon as the class is applied. This keeps the animation in CSS and uses JavaScript only as the trigger.

How do I restart a CSS animation with JavaScript?+

Remove the animation class, force a reflow by reading the element's offsetWidth (void element.offsetWidth), then add the class back. The reflow makes the browser reset the animation so it plays again instead of doing nothing.

How do I trigger an animation when an element scrolls into view?+

Use IntersectionObserver. Observe the elements, and in the callback add an 'in-view' class when entry.isIntersecting is true, then unobserve to animate only once. It's far more performant than listening to scroll events.

Should I use CSS animation or GSAP?+

Use plain CSS with a class toggle for simple hovers, toggles and reveals with no dependency. Use GSAP when you need timelines that sequence many steps, scroll-scrubbing with ScrollTrigger, staggering many elements, or physics-based easing. Match the tool to the complexity of the motion.

What is the Web Animations API?+

The Web Animations API lets you create and control animations from JavaScript using element.animate(keyframes, options). It returns an Animation object you can pause, reverse, and await, giving you JavaScript control over motion without needing a library.

Want this done for your site?

I build fast, SEO-ready sites and rank them on Google and AI search.