Skip to content

Web Development · Animation

Web Animation in 2026: GSAP, Framer Motion, and When to Use the Platform

Three animation options dominate web development right now: CSS, Framer Motion, and GSAP. Here's how to pick the right one for your project and why the native platform is better than you might expect.

Anurag Verma

Anurag Verma

9 min read

Web Animation in 2026: GSAP, Framer Motion, and When to Use the Platform

Sponsored

Share

Animation is one of those topics where developers either reach for a heavy library by default or try to avoid it entirely because the options seem complicated. Both instincts lead to worse outcomes than understanding what each tool is actually good at.

Things are cleaner in 2026 than they were a few years ago. Native browser animation capabilities have caught up to what used to require JavaScript libraries. GSAP remains the benchmark for complex, sequence-driven animation. Framer Motion has found its niche in React UIs. And CSS animations are the right answer more often than developers realize.

Here’s how to actually choose.

Start With What the Browser Gives You

CSS transitions and keyframe animations run on the browser’s compositor thread, not the main JavaScript thread. This means they don’t get blocked by heavy JavaScript work and typically produce smoother frame rates than JS-based animation. For simple animations, this is the right default.

CSS transitions for state changes:

.button {
  background-color: #2563eb;
  transform: scale(1);
  transition: background-color 200ms ease, transform 150ms ease;
}

.button:hover {
  background-color: #1d4ed8;
  transform: scale(1.02);
}

.button:active {
  transform: scale(0.98);
}

CSS keyframes for anything looping or multi-step:

@keyframes fade-in-up {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.card {
  animation: fade-in-up 400ms ease forwards;
}

CSS View Transitions for page-level and component-level transitions. This API landed in all major browsers in 2024 and handles the “morph element from one state/page to another” pattern that used to require FLIP animation libraries:

// Trigger a view transition
document.startViewTransition(() => {
  // Update the DOM here
  container.innerHTML = newContent;
});
/* Optional: customize the transition */
::view-transition-old(root) {
  animation: 300ms ease slide-out;
}

::view-transition-new(root) {
  animation: 300ms ease slide-in;
}

/* Name specific elements for matched transitions */
.product-card {
  view-transition-name: product-card;
}

When you name an element with view-transition-name and that same name appears on an element in the new DOM state, the browser animates between them automatically. This is how you get the “shared element transition” effect: an image that smoothly morphs from a grid thumbnail to a full-page hero, without any JavaScript animation logic.

The Web Animations API is the JavaScript interface to the same compositor-thread animation system:

const element = document.querySelector('.card');

const animation = element.animate(
  [
    { opacity: 0, transform: 'translateY(20px)' },
    { opacity: 1, transform: 'translateY(0)' }
  ],
  {
    duration: 400,
    easing: 'ease',
    fill: 'forwards'
  }
);

// Wait for completion
await animation.finished;

WAAPI gives you programmatic control (pause, reverse, playback rate) with the performance of CSS animations. It’s underused.

When CSS / WAAPI is enough:

  • Hover states and button feedback
  • Loading spinners and skeleton screens
  • Simple entrance animations (fade, slide)
  • Page transitions with View Transitions API
  • Anything that can be described with start state, end state, and easing

Framer Motion for React UI Animations

Framer Motion is a React animation library. It wraps elements in animated components and provides a declarative API for animations tied to component state and lifecycle.

npm install framer-motion

The motion component wraps any HTML element and accepts animation props:

import { motion } from 'framer-motion';

function Card({ visible }) {
  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: visible ? 1 : 0, y: visible ? 0 : 20 }}
      transition={{ duration: 0.3, ease: 'easeOut' }}
    >
      Content here
    </motion.div>
  );
}

Where Framer Motion is particularly strong:

AnimatePresence for mount/unmount animations. When a component is removed from the DOM, CSS transitions can’t animate the exit because the element is gone. Framer Motion defers the removal until the exit animation finishes:

import { motion, AnimatePresence } from 'framer-motion';

function Toast({ messages }) {
  return (
    <AnimatePresence>
      {messages.map(message => (
        <motion.div
          key={message.id}
          initial={{ opacity: 0, x: 50 }}
          animate={{ opacity: 1, x: 0 }}
          exit={{ opacity: 0, x: 50 }}
          transition={{ duration: 0.2 }}
        >
          {message.text}
        </motion.div>
      ))}
    </AnimatePresence>
  );
}

Layout animations for when elements change position or size due to DOM changes:

import { motion, LayoutGroup } from 'framer-motion';

function SortableList({ items }) {
  return (
    <LayoutGroup>
      {items.map(item => (
        <motion.div key={item.id} layout>
          {item.name}
        </motion.div>
      ))}
    </LayoutGroup>
  );
}

When layout is set, Framer Motion uses FLIP animation under the hood. It reads the element’s position before and after a DOM change and animates between them. Items reordering in a list, a sidebar expanding, a grid reshuffling all animate smoothly.

Variants for coordinating animations across multiple elements:

const container = {
  hidden: { opacity: 0 },
  visible: {
    opacity: 1,
    transition: {
      staggerChildren: 0.1,  // Each child delays by 100ms
    }
  }
};

const item = {
  hidden: { opacity: 0, y: 20 },
  visible: { opacity: 1, y: 0 }
};

function List({ items }) {
  return (
    <motion.ul variants={container} initial="hidden" animate="visible">
      {items.map(i => (
        <motion.li key={i.id} variants={item}>
          {i.name}
        </motion.li>
      ))}
    </motion.ul>
  );
}

When Framer Motion fits:

  • React applications with component mount/unmount animations
  • List reordering and layout shifts that need smooth interpolation
  • Gesture-based interactions (drag, pan, pinch with inertia)
  • Multi-component choreography where timing relationships matter
  • Teams already using React who want declarative animation code

The tradeoff: Framer Motion adds ~50KB to your bundle (gzipped). For a marketing site where every kilobyte counts, this matters. For a web application where users are already logged in and the bundle is large, it matters less.

GSAP for Complex Animation Work

GreenSock Animation Platform has been the professional standard for complex web animation since Flash died. It’s what motion designers reach for, what banner ad studios use, what interactive agencies ship. It’s not the right tool for most UI animation, but it’s the best tool when you need what it does.

npm install gsap

The core API is a timeline of tweens:

import gsap from 'gsap';

// Animate a single element
gsap.to('.hero-title', {
  opacity: 1,
  y: 0,
  duration: 0.8,
  ease: 'power2.out'
});

// Sequence with a timeline
const tl = gsap.timeline();

tl.from('.nav', { y: -60, opacity: 0, duration: 0.5 })
  .from('.hero-title', { y: 40, opacity: 0, duration: 0.7 }, '-=0.2')
  .from('.hero-subtitle', { y: 30, opacity: 0, duration: 0.5 }, '-=0.3')
  .from('.hero-cta', { scale: 0.9, opacity: 0, duration: 0.4 }, '-=0.2');

The -=0.2 parameter means “start this tween 0.2 seconds before the previous one ends.” This overlap control is what makes GSAP timelines precise for choreographed sequences.

ScrollTrigger (a GSAP plugin) ties animations to scroll position:

import gsap from 'gsap';
import ScrollTrigger from 'gsap/ScrollTrigger';

gsap.registerPlugin(ScrollTrigger);

gsap.to('.progress-bar', {
  scaleX: 1,
  transformOrigin: 'left center',
  ease: 'none',
  scrollTrigger: {
    trigger: document.body,
    start: 'top top',
    end: 'bottom bottom',
    scrub: true  // Links animation progress to scroll position
  }
});

// Pin an element while scrolling
gsap.to('.sticky-panel', {
  scrollTrigger: {
    trigger: '.sticky-section',
    start: 'top top',
    end: 'bottom bottom',
    pin: true,
    scrub: 1
  }
});

GSAP also handles SVG animation, morphing between SVG paths, physics-based motion, and 3D transforms. These are impractical with CSS or Framer Motion.

When GSAP is the right choice:

  • Complex marketing sites with story-driven scroll animations
  • Interactive data visualizations with animated transitions
  • SVG animation and path morphing
  • Sequences with precise timing across many elements
  • Work where a motion designer is handing you specs that need exact control

The tradeoff: GSAP’s free tier is for general use and can be used in most projects. The premium plugins (ScrollSmoother, MorphSVG, DrawSVG, SplitText) require a paid Club GreenSock membership or a commercial license. The free ScrollTrigger covers most scroll animation needs. Check the license before shipping paid-plugin features in a client project.

The Decision Tree

Is this a React application?
├── Yes → Need mount/unmount animations?
│         ├── Yes → Framer Motion (AnimatePresence)
│         └── No → Need gesture/drag?
│                  ├── Yes → Framer Motion
│                  └── No → CSS / WAAPI first, Framer Motion if it gets complex
└── No → Is this a complex scroll-driven or multi-element sequence?
          ├── Yes → GSAP + ScrollTrigger
          └── No → CSS / View Transitions / WAAPI

The most common mistake is reaching for GSAP or Framer Motion because the animation feels important, then shipping a 200KB dependency to animate a button hover. The second most common mistake is trying to do mount/unmount animation with plain CSS and fighting the browser’s behavior.

Match the tool to the problem. For state changes and simple transitions, the browser’s native capabilities are more than sufficient. For React UIs with components that appear and disappear, Framer Motion handles the lifecycle cleanly. For complex, sequenced, scroll-driven animation where you need precise control, GSAP is the right tool.

Performance: What to Watch

Regardless of the tool, the same CSS properties animate cheaply and others are expensive.

Cheap (compositor-thread, no layout recalculation):

  • transform (translate, scale, rotate)
  • opacity

Expensive (triggers layout or paint, main thread):

  • width, height, margin, padding, top, left
  • background-color (triggers paint, but usually fast enough)
  • box-shadow (can be expensive on complex elements)

Animate transform: translateX() instead of left, transform: scale() instead of width/height. This applies to all three tools. GSAP and Framer Motion both have fallbacks for animating layout properties, but you pay the performance cost the same way.

The Chrome DevTools Performance panel shows you frame timing. Look for frames over 16ms (60fps budget) when your animations are running. If you see main thread activity during animations, you’re animating layout properties or running expensive JavaScript on each frame.

Modern web animation is in good shape. The platform alone handles more use cases than it used to. When you do reach for a library, make sure you’re reaching for the right one.

Sponsored

Enjoyed it? Pass it on.

Share this article.

Sponsored

The dispatch

Working notes from
the studio.

A short letter twice a month — what we shipped, what broke, and the AI tools earning their keep.

No spam, ever. Unsubscribe anytime.

Discussion

Join the conversation.

Comments are powered by GitHub Discussions. Sign in with your GitHub account to leave a comment.

Sponsored