Skip to content
Innovialabs

Web Animations: CSS vs JavaScript, GSAP and Motion

Practice3 min

In short

Handle simple state changes (hover, reveal, expand) with CSS and complex sequences or scroll-driven scenes with GSAP or Motion; animate only transform and opacity, and turn motion down for prefers-reduced-motion.

The short answer

  • CSS transitions and keyframes — for hover, focus, opening menus, revealing elements, looping loaders. Cheap, no library needed, and they work even before JavaScript loads.
  • GSAP — for complex timelines, syncing many elements, scroll animations with pinned sections, SVG animation.
  • Motion — for React interfaces: components entering and leaving, list reordering, gestures, state-to-state transitions. There is also a version for plain JavaScript.

The rule of thumb: if the animation is "from state A to state B on an event", use CSS. If you need sequences, time control, scroll linking or physics, use a JavaScript library.

Which properties animate cheaply

The browser renders a frame in stages: layout (computing sizes), paint (drawing pixels) and composite (combining layers). The earlier the stage an animation touches, the more expensive it is.

PropertyWhat gets recalculatedCost
transform, opacitycomposite onlycheap
color, background-color, box-shadowpaintmedium
width, height, top, left, margin, paddinglayout + paintexpensive

Use transform: translateX() instead of left, and transform: scale() instead of width. To animate a shadow cheaply, fade in a pseudo-element that already has the shadow via opacity.

will-change: transform hints the browser to promote an element to its own layer. Use it sparingly and only on things that actually animate: extra layers cost memory.

CSS: when it is enough

.card {
  transition: transform 200ms ease, opacity 200ms ease;
}
.card:hover {
  transform: translateY(-4px);
}

@media (prefers-reduced-motion: reduce) {
  .card { transition: none; }
  .card:hover { transform: none; }
}

For scroll-based effects, modern browsers now offer scroll-driven animations (animation-timeline: view()) with no JavaScript. Support is still incomplete, so check it and make sure content stays visible when the animation does not run.

GSAP and Motion: scroll-triggered animation

A simple reveal on scroll can be done with Intersection Observer and a CSS class. If you need pinned sections, animation tied to scroll position or complex sequences, a library is more convenient.

GSAP with ScrollTrigger, respecting user preferences:

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

const mm = gsap.matchMedia();
mm.add("(prefers-reduced-motion: no-preference)", () => {
  gsap.from(".feature", {
    y: 40,
    opacity: 0,
    stagger: 0.1,
    scrollTrigger: { trigger: ".features", start: "top 80%" }
  });
});

Motion in React:

import { motion, MotionConfig } from "motion/react";

<MotionConfig reducedMotion="user">
  <motion.div
    initial={{ opacity: 0, y: 24 }}
    whileInView={{ opacity: 1, y: 0 }}
    viewport={{ once: true }}
  />
</MotionConfig>

reducedMotion="user" disables transform animations for people who turned on reduced motion in their system settings.

prefers-reduced-motion

For some people, on-screen motion causes dizziness and nausea. The prefers-reduced-motion media query tells you the user asked the system to reduce animation. What to do:

  • remove parallax, large movements and auto-scrolling;
  • keep short opacity and color changes — they are usually fine;
  • make sure content is visible without animation and does not get stuck at opacity: 0.

Avoiding jank

  • Animate transform and opacity, not sizes and spacing.
  • Do not interleave reading and writing layout (offsetHeight, then a style write, then another read) — it forces synchronous layout.
  • Drive JavaScript animations with requestAnimationFrame or a library, not setInterval.
  • Do not attach heavy logic to the scroll event.
  • Test on a low-end phone and in the DevTools Performance panel with CPU throttling.
  • Do not animate everything: one expressive animation beats ten small ones.

FAQ

For transform and opacity the difference is usually negligible: CSS and good libraries rely on the same fast browser mechanisms. Animations become slow because of properties that trigger layout, not because of the technology chosen.

We can help with this

If you'd rather get the result than figure it all out yourself — this is what we do every day.

Discuss a project

Read next

Have an idea or a task? Tell us about it

We'll reply within one business day and propose a solution. The consultation is free.