Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 18.3Advanced ~20 min · shot 3 of 6

Choosing a Tool and Its Cost

In one line

CSS, the Web Animations API, GSAP, Motion, React Spring, Lottie, Rive and canvas each do some jobs best. Choose by what you need to control, then check the cost: bytes over the network, who runs the animation, and what the page does when the tool fails to load.

0/6 · 0%

Think of it like this

A toolbox with a hammer, a drill and a laser level. The laser level is wonderful on a wall of shelves and silly for hanging one picture, and it weighs the same in your bag. A library is the same: great for its job, a cost on every page that loads it.

Words you'll meet

New words in this topic, in plain English. Come back here whenever one feels fuzzy.

bundle
The JavaScript file your build makes from your code and the libraries it imports.
gzipped
Compressed the way a server sends a file. The gzipped size is what travels over the network.
fallback
What the page does when a tool is missing or fails. For motion, it is the content without the effect.
tree shaking
A build step that drops library code you never import.

Step by step

01Ask the browser who runs it

Run the first example. After 60 ms: CSS 1 (CSSTransition), WAAPI 1 (Animation), GSAP 0, Motion 1 (Animation).

02Measure the bytes

Bundle a one-line import of each tool with esbuild (bundle and minify) and compress it with zlib.gzipSync. We ran that for every row.

terminal
$ node size.cjs # esbuild bundle + gzip of one import per row
── expected output ──
measured 2026-10-23 with esbuild 0.28.2 minified gzip brotli
GSAP core (gsap.to) 70704 27677 25032
GSAP core + ScrollTrigger 115251 45283 40718
motion: animate (vanilla) 64627 24226 22016
motion/mini: animate (WAAPI only) 8406 3348 3048
motion/react: motion.div 134662 45350 40574
motion/react: LazyMotion + m (domAnimation) 86211 31223 28368
@react-spring/web: useSpring + animated 42891 17387 15648
@formkit/auto-animate 7854 3126 2785
dotlottie-web (JavaScript only) 61642 13853 12246
dotlottie player .wasm (separate file) 1238072 499096 388165
Packages as pinned in this course (gsap 3.15.0, motion 14.3.0, @react-spring/web 10.1.2, @formkit/auto-animate 0.10.0, @lottiefiles/dotlottie-web 0.81.0). React and react-dom are left out of the React rows. CSS and the Web Animations API cost 0 bytes.

03Walk the decision

Answer the questions in order and stop at the first yes.

Walk the decisiondiagram
Rendering diagram…

04Plan for failure

Load each library with import() inside a try and keep a CSS version of the arrival (second example). Content must never wait for a library.

Try it yourself

  1. 1

    Remove the wait

    In "The same fade, four ways" delete the await new Promise(...) line. CSS and WAAPI still say 1 but Motion says 0: its animation starts on the next frame. Read the count too early and you conclude the wrong thing.

  2. 2

    Make the library load

    In "A library that cannot load" add import "gsap"; as the first line (a page with an import is a module, so a bare package name resolves) and change the URL to "gsap". The log says library loaded: true, box shown: by GSAP. Without the import line the call fails with Failed to resolve module specifier 'gsap'.

Code & diagrams

The same fade, four ways

A 400 ms fade started four ways. After 60 ms the script asks each element how many animations the browser has for it.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

CSS     1 browser animation(s): CSSTransition
WAAPI   1 browser animation(s): Animation
GSAP    0 browser animation(s)
Motion  1 browser animation(s): Animation
A library that cannot load

The preview has no network, like an offline visitor. The `import()` rejects with the real error text, the `catch` returns null and the box arrives with plain CSS.

Press Run to see the page.
00:00.000
animations
0.00s0.10s0.20s0.30s0.40s0.50s0.60s0.70s0.80s0.90s

Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.

frames

Console

Nothing logged yet.

Expected console output

TypeError: Failed to fetch dynamically imported module: https://cdn.example.com/gsap/3.15.0/gsap.min.js
library loaded: false, box shown: by CSS

Break it on purpose

Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.

Break #1

Show the content only after the library loads

In "A library that cannot load" delete else box.classList.add("in");.

terminal
$ Run the example
── what you'll see ──
TypeError: Failed to fetch dynamically imported module: https://cdn.example.com/gsap/3.15.0/gsap.min.js
library loaded: false, box shown: by GSAP

Myth vs fact

Myth

A bigger library is always slower.

Fact

Bytes cost download and parse time on every visit; they say nothing about how smoothly one animation runs. A tiny helper that animates width can stutter, and a big library driving transform can be smooth. Check bytes and the track's cost colour.

Pro corner

Extra depth for experienced readers. New to this? Skip it for now and come back later.

  • ▸

    Sizes depend on the entry point: motion/mini is WAAPI-only at 3,348 bytes gzipped, animate from motion carries springs and sequences at 24,226. The preview of this course offers only motion and motion/react, so that row was measured locally.

Remember this

  1. 1

    The problem this solves: teams add a 45 KB library to fade one box, or fight plain CSS to sequence thirty elements for a week. Choosing by habit costs users bytes and the team time. Choose by what the motion needs, then measure what the choice costs.

  2. 2

    What each is best at. CSS transitions and keyframes: state changes and simple loops; no JavaScript, and the browser runs and reverses them. The Web Animations API (element.animate): the same engine from JavaScript, for run-time values, with pause, currentTime and a finished promise; no library bytes. GSAP: timelines, stagger, scroll scrubbing and pinning, SVG plugins (Phase 14). Motion: springs and values, using the browser where it can; Motion for React adds AnimatePresence and layout animation (Phase 15). React Spring: physics for React values. Lottie and Rive: a designer's motion as a file plus a player (Phase 16). Canvas: thousands of moving things (Phase 17).

  3. 3

    Who runs it matters. The first example starts the same 400 ms fade four ways and asks the browser how many animations it knows about. CSS reports a CSSTransition, the Web Animations API an Animation, and Motion an Animation after one frame (its call returns at once; the browser animation starts on the next frame). GSAP reports none: it writes a new style value on every frame from JavaScript, so the browser sees no animation to run on the compositor. The result looks the same; the cost and the failure modes differ (Topic 6.1).

  4. 4

    Bytes: measured. The terminal bundles a one-line import of each package with esbuild 0.28.2 (minified, browser, React left out) and compresses it: GSAP core 27,677 bytes gzipped, 45,283 with ScrollTrigger; Motion's animate 24,226 and its WAAPI-only mini build 3,348; React Spring 17,387; auto-animate 3,126; the dotLottie player 13,853 plus a separate 499,096-byte WASM file downloaded when the player starts. These are minimal imports; real use pulls in more. Measure your own bundle.

  5. 5

    A decision, not a ranking. Start with CSS. If the value is known only at run time, or you need to pause or seek, use the Web Animations API. Reach for a library when it removes real work: sequencing (GSAP), springs and gestures (Motion), component exit and layout (Motion for React). Use a file format when a designer owns the motion, and canvas when there are too many things for the page. The chart below follows those questions.

  6. 6

    Marketing page or dashboard. The Lumen launch page needs a fast first load and a few expressive moments, so it uses CSS and the Web Animations API almost everywhere and GSAP only for the launch timeline. A dashboard wants quiet CSS transitions and tokens, and no animation library.

  7. 7

    When the tool fails. A library can fail to load (offline, blocked, a bad deploy). The second example imports GSAP from a URL that cannot load: the real error is TypeError: Failed to fetch dynamically imported module, the catch runs and the same arrival plays in plain CSS. Content must never depend on a library to become visible.

Explain it without notes

01

Why measure bundle size from the installed packages instead of quoting a number?

02

How do the CSS, WAAPI, GSAP and Motion fades differ for the browser in the first example?

03

What should happen when an animation library fails to load?

Practice

01

Write fadeIn(el) with the Web Animations API that respects reduced motion and returns a promise.

02

Say why the second example runs the fallback in CSS rather than waiting for GSAP.

Done when you can

  • You can name what CSS, WAAPI, GSAP, Motion, Lottie and canvas are each best at

  • You can measure a package's cost by bundling a minimal import and compressing it

  • You can explain why GSAP shows no browser animation and Motion shows one a frame later

  • You can load a library safely and keep a working fallback