Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 16.1Advanced ~22 min · shot 1 of 6

Video, GIF, Sprite Sheet, Lottie or Rive?

In one line

A designer's animation can reach the page as a video, a GIF, a sprite sheet, a Lottie file or a Rive file. Each trades file size, work for the browser, control from code, and accessibility. Measure before choosing.

0/6 · 0%

Think of it like this

A puppet show can be filmed and played on a screen, drawn as a flip-book, or performed live with the puppets in the room. The film is simple to show but fixed. The flip-book is heavy and cannot be paused nicely. The live puppets can react to the audience, but you must bring the puppets and a puppeteer. The five file formats map to these choices.

Words you'll meet

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

sprite sheet
One picture holding every frame of an animation in a grid. CSS shows one cell at a time.
codec
The method a video or audio file uses to squeeze data (for example H.264 or VP9).
alpha
The part of a picture that says how see-through each pixel is. GIF has only on or off.
decoded size
How many bytes a picture takes in memory once unpacked: width × height × 4. It does not depend on the file's size.
autoplay policy
A browser rule that blocks media with sound from starting until the visitor has interacted with the page.
Lottie
A JSON format for vector animation, first made for the After Effects tool, played by many players.

Step by step

01Render the mascot to frames

We rendered all 120 frames of the headlight (200 × 200, 30 fps, white background) with the dotLottie player, one PNG per frame. Every number in the next steps comes from these frames.

02Encode four ways with ffmpeg

The same frames became a GIF (ffmpeg's own palette step), an H.264 MP4, a VP9 WebM at quality 33, and one PNG sprite sheet of 12 columns by 10 rows. Then we counted the bytes.

terminal
$ ffmpeg -framerate 30 -i f%03d.png -c:v libx264 -pix_fmt yuv420p out.mp4 (and the GIF, WebM and tile variants)
wc -c out.gif out.mp4 out.webm sheet.png headlight.json headlight.lottie
── expected output ──
78953 out.gif
17183 out.mp4
27956 out.webm
252272 sheet.png
6592 headlight.json
1150 headlight.lottie

03Read the numbers honestly

Lottie (1,150 bytes) is about 15 times smaller than the MP4 and 69 times smaller than the GIF. Our mascot is flat and simple, so this is a best case: a real designer's file with many shapes is larger, and one with embedded photos can be megabytes. We did not measure such a file.

One encoder setting was used for each format. A tuned GIF optimiser or a different MP4 quality would move the numbers. They show the order of size, not a law.

04Add the cost of the player

The dotLottie player is a 165,443-byte JavaScript file (33,080 gzipped) plus a 1,238,072-byte WASM file (497,567 gzipped), measured in the installed package with gzip at its default level. That is about 530 KB over the wire, far more than the 17 KB video. For one tiny mascot the video is cheaper; Lottie wins when you have many animations, need control, or need sharpness at any size.

05Decide

The chart below is a starting point, not a verdict. Always ask what the visitor sees with reduced motion on, and what they see when the file fails.

Decidediagram
Rendering diagram…

Try it yourself

  1. 1

    Add a second missing file

    In the sizes example, add "headlight.riv" to the list. The console gets one more line, headlight.riv: HTTP 404, and nothing crashes: the loader reports the status instead of throwing.

  2. 2

    Make the sound clip play after a click

    Press Play with sound (a click) in the autoplay example. The text under the console changes to with sound, after a click: playing: the click is the permission the first two tries lacked.

Code & diagrams

The sizes, and a 404 that does not throw

The two real files of this course, then a GIF that does not exist: `fetch()` does not throw on a 404, so a loader must check `res.ok`. The last two lines are arithmetic: a decoded frame is 4 bytes per pixel, so 120 frames kept as a sprite sheet take 19.2 MB of memory no matter how small the PNG file was.

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

headlight.lottie: 1150 bytes
headlight.json: 6592 bytes
headlight.gif: HTTP 404
one frame, 200 x 200 x 4 bytes: 160000
120 frames kept decoded as a sprite sheet: 19200000 bytes
A video with sound refuses to start

`play()` returns a promise. Without a click the browser rejects it with `NotAllowedError` when the clip has sound; muted it plays. Press the button to see it play after a click. (It must be a real click or tap: in our test a click made by script, `button.click()`, still got `NotAllowedError`, because it is not a visitor's gesture.) A hero video must be `muted`, `playsinline`, and have a poster.

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

with sound, no click: NotAllowedError
muted, no click: playing
A one-row sprite sheet in CSS (not run here)css
/* 24 cells of 200 x 200 side by side: a picture 4800 x 200. steps(24) jumps one cell at a time. */
.sprite { width: 200px; height: 200px; background: url(headlight-row.png) 0 0 / 4800px 200px; animation: play 0.8s steps(24) infinite; }
@keyframes play { to { background-position: -4800px 0; } }
/* A grid like our 12 x 10 sheet needs two moves (x and y), so long loops are awkward in pure CSS. */
A poster for reduced motion around a GIF (not run here)html
<!-- A GIF cannot be paused by the page. A picture element can at least swap in a still: -->
<picture>
  <source media="(prefers-reduced-motion: reduce)" srcset="headlight-still.png">
  <img src="headlight.gif" alt="Lumen headlight waving" width="200" height="200">
</picture>

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

Forget `res.ok`

In the sizes example, replace the size function body with return (await (await fetch("/data/anim/" + name)).arrayBuffer()).byteLength + " bytes";.

terminal
$ Press Run
── what you'll see ──
`headlight.gif: 9 bytes` in the checker (the size of its plain 404 text): the error page is counted as if it were a GIF.

Myth vs fact

Myth

GIF is the safe choice because it works everywhere.

Fact

It works everywhere and is the biggest of our four encodes (78,953 bytes against 17,183 for MP4), limited to 256 colours, and cannot be paused by the page.

Myth

Lottie is always the smallest option.

Fact

The file is small, but the player is about 530 KB. For one small animation a video can cost less in total.

Pro corner

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

  • ▸

    Decode cost matters as much as file size. A 12 × 10 sprite sheet of 200 × 200 cells is a 2400 × 2000 texture: 19.2 MB of decoded pixels the browser may hold on the GPU. A 252 KB file does not mean a 252 KB memory cost.

  • ▸

    The sizes above come from one machine and one encoder version (ffmpeg 8.1.1). Re-run them for your own art: encoder settings, frame count and colour count change the order of the results.

  • ▸

    Vendors publish size comparisons between Lottie and Rive. Both are written by the people who sell one of them. Treat them as a hint and measure your own files.

Remember this

  1. 1

    The problem this solves: teams pick a format by habit ("export a GIF") and find the cost later: a page that weighs 2 MB more, a phone that gets warm, a mascot that cannot be paused for reduced motion. The right choice depends on four questions: how many bytes, how much work per frame, can code control it, and can a visitor who asks for less motion be served.

  2. 2

    Video (MP4, WebM) stores pictures that change little from frame to frame very compactly, and phones decode it with dedicated hardware. It is the cheapest for photographic or complex scenes. The cost: it is a fixed picture (you cannot restyle it), a plain <video> has no transparency in H.264, and a video with sound will not autoplay until the visitor has interacted with the page (measured below).

  3. 3

    GIF is a 1987 format: at most 256 colours per frame and no partial transparency, so smooth edges look rough. It is large for what it shows, and a page cannot pause or seek an <img> GIF from script. It survives for one reason: it works everywhere with no code. For new work it is nearly always the wrong choice.

  4. 4

    A sprite sheet is all the frames drawn in one big picture, shown one cell at a time by moving the picture with CSS steps() (Topic 4.4). It keeps transparency and plays on the compositor, but the whole sheet is decoded into memory as pixels (4 bytes per pixel), whatever its file size. Good for a short loop of a few frames, poor for 120.

  5. 5

    Lottie is the animation as data: a JSON file of layers, shapes and keyframes that a player draws every frame (Topic 16.2). It stays sharp at any size, is tiny for flat, vector-style art, and code can play segments, change speed and react to events. The cost: it needs a player (a library and, for dotLottie, a WASM file), and it only holds what the player can draw (no photos without large embedded images).

  6. 6

    Rive is also data, made in the Rive editor, and its files carry a state machine: named states, inputs and transitions, so the animation itself decides how to react to a hover or a click (Topic 16.4). It has its own runtime. Measured claims that one is smaller or faster than the other come from vendors; this course found no independent benchmark, and says so.

  7. 7

    Decide by measuring. The walkthrough below encodes our mascot four ways and counts the bytes. The result is not what habit predicts: for a 4-second, 200 × 200 mascot the Lottie file is the smallest, but the player that draws it is far larger than any of the others. Topic 16.5 turns that into a rule: load the player late.

Explain it without notes

01

Why does the video tag need muted to autoplay, and what does play() do when the browser refuses?

02

Give two reasons a 200-frame sprite sheet can be a worse choice than its file size suggests.

03

When is a video a better choice than a Lottie file?

Practice

01

Take any animation you have as a GIF. Encode it as an MP4 with ffmpeg -i in.gif -movflags faststart -pix_fmt yuv420p out.mp4 and compare the byte counts.

02

Write the <video> markup for a muted looping hero clip that also respects reduced motion by showing a poster.

Trade-offs

  • ↔

    Video: smallest for complex scenes, hardware decoded, fixed and hard to make transparent. Lottie: sharp, controllable and tiny files, but a large player. Rive: built-in state logic, its own runtime. GIF: no code needed, large and uncontrollable.

Done when you can

  • You can name the five formats and one strength and one cost of each

  • You can measure a file in bytes and also say what it costs decoded and to play

  • You can explain why a video with sound does not autoplay, and handle the rejected promise

  • You can say what a visitor with reduced motion should get for each format