Shots in this scene
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.
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.
03Walk the decision
Answer the questions in order and stop at the first yes.
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
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
Make the library load
In "A library that cannot load" add
import "gsap";as the first line (a page with animportis a module, so a bare package name resolves) and change the URL to"gsap". The log sayslibrary loaded: true, box shown: by GSAP. Without theimportline the call fails withFailed to resolve module specifier 'gsap'.
Code & diagrams
A 400 ms fade started four ways. After 60 ms the script asks each element how many animations the browser has for it.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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): AnimationThe 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.
Nothing is moving yet. Hover, click or scroll the page: every animation it starts shows up here as a track.
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 CSSBreak 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");.
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/miniis WAAPI-only at 3,348 bytes gzipped,animatefrommotioncarries springs and sequences at 24,226. The preview of this course offers onlymotionandmotion/react, so that row was measured locally.
Remember this
- 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
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, withpause,currentTimeand afinishedpromise; 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 addsAnimatePresenceand 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
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 anAnimation, and Motion anAnimationafter 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
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
animate24,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
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
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
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, thecatchruns and the same arrival plays in plain CSS. Content must never depend on a library to become visible.
Explain it without notes
Why measure bundle size from the installed packages instead of quoting a number?
How do the CSS, WAAPI, GSAP and Motion fades differ for the browser in the first example?
What should happen when an animation library fails to load?
Practice
Write fadeIn(el) with the Web Animations API that respects reduced motion and returns a promise.
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