Command Palette

Search for a command to run...

Hectal
Shots in this scene
SHOT 16.2Advanced ~21 min · shot 2 of 6

Lottie: Animation as JSON

In one line

A Lottie file is a recipe: layers, shapes and keyframes written as JSON. A player redraws it every frame, so it stays sharp at any size. A .lottie file is the same recipe zipped. A player ignores what it cannot draw, without an error.

0/6 · 0%

Think of it like this

Sheet music. The paper does not contain sound; it holds notes, timing and instructions, and a musician (the player) performs it. Two musicians can play it at different sizes of hall. A Lottie file holds shapes, positions and timing, and a player draws them. A recording (video) would fix one performance.

Words you'll meet

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

layer
One drawn item in the animation with its own transform and shapes. The first layer in the list is drawn on top.
keyframe
A value stored for one frame. The player works out the frames between two keyframes.
marker
A named stretch of the timeline, stored as a start frame and a length.
dotLottie
The .lottie file: a ZIP with a manifest, the animation JSON and optional images, themes and state machines.
player
The library that reads the file and draws each frame onto a canvas.
expression
A small script inside a Lottie property (such as wiggle). Players that do not support them skip it.

Step by step

01Open the JSON as data

The first example reads headlight.json and prints its header, its layers, how many keyframes each one has, and its markers. You are looking at the whole animation as text: 6,592 characters.

02Find the three stretches

The markers give greet 0+45, blink 45+15 and idle 60+60: start frame plus length. At 30 fps they are 1.5 s, 0.5 s and 2 s, and together they cover the 4 seconds (120 frames).

03Edit the file and load it

The second example changes the JSON in memory and gives it to the player with the data option. It loads five variants and compares the pixels at frame 30 with the original.

04Read the verdicts

Three variants load and look identical (their feature is ignored). One breaks the file's structure and fails with Invalid Lottie JSON object. A player is not a validator: it draws what it understands.

Try it yourself

  1. 1

    Change a colour in the data

    Add "face colour red": edit((j) => { j.layers[1].shapes[1].it[1].c.k = [1, 0, 0, 1]; }), to the cases. It prints load, different picture at frame 30, and on the canvas the lamp's orange body is red (the cream face and the dark outline stay as they were): colours in Lottie are red, green, blue, alpha from 0 to 1.

  2. 2

    Set the frame rate to 0

    Add "frame rate 0": edit((j) => { j.fr = 0; }),. It loads without an error and gives the same picture at frame 30. The file's fr is 0 but the player did not reject it. If you also print p.duration after load (the player's duration is in seconds), it says Infinity while p.totalFrames is still 120: frames divided by a rate of 0. Check a file's numbers before you trust a duration.

Code & diagrams

What is inside the mascot's file

The animated values are counted as `{ a: 1, k: [...] }` arrays anywhere inside a layer, so `eyes` (two eyes, each scaled and moved) has the most. `bracket` has none: it never moves.

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

version 5.7.4, 30 fps, frames 0 to 120, 200 x 200
layers, top first: eyes, lamp, beam, bracket
eyes: type 4, 26 keyframes
lamp: type 4, 10 keyframes
beam: type 4, 9 keyframes
bracket: type 4, 0 keyframes
markers: greet 0+45, blink 45+15, idle 60+60
assets: 0, size as text: 6592 characters
Features the player ignores, and a file it rejects

Each variant loads from edited JSON through the `data` option. The `[error]` line is the library's own console message; the next line is the `loadError` event. The picture comparison uses a hash of the canvas at frame 30.

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

as shipped: load, same picture at frame 30
unknown layer type (ty 99): load, same picture at frame 30
blur effect on a layer (ef): load, same picture at frame 30
expression on a property: load, same picture at frame 30
Invalid Lottie JSON object: The provided object does not conform to the Lottie JSON format.
no layers key: loadError Error: Invalid Lottie JSON object: The provided object does not conform to the Lottie JSON format.

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

Hand the player cut-off text

In the second example, replace data: make() with data: JSON.stringify(make()).slice(0, 3000).

terminal
$ Press Run
── what you'll see ──
The first four variants print `loadError Error: Invalid Lottie JSON string: The provided string does not conform to the Lottie JSON format.`, each after an `[error]` line with the same text. The last one, `no layers key`, is shorter than 3000 characters, so the cut does nothing to it: it arrives whole and fails the other way, with `loadError Error: failed to load`.

Myth vs fact

Myth

If it plays in After Effects or the design tool's preview, it plays on the web.

Fact

A player draws only what it supports. In our test unsupported features loaded with no error and no change in the picture, so the failure is silent.

Myth

A Lottie file is always small.

Fact

Vector data is small. Embedded pictures are base64 text, a third bigger than the picture itself.

Pro corner

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

  • ▸

    The easing handles i and o use the same cubic Bézier idea as CSS. A Lottie keyframe can also be a hold (h: 1), which jumps instead of tweening.

  • ▸

    ip and op set the start and end of the whole animation and each layer has its own, so a layer can exist for only part of the timeline.

  • ▸

    The dotLottie manifest can hold several animations, themes (colour sets) and state machines. Our make-mascot.cjs writes a minimal manifest with one animation and no theme: setTheme() returns false and stateMachineLoad() returns false on it, as measured.

Remember this

  1. 1

    The problem this solves: a designer works in a vector tool such as After Effects and needs the result on a web page without a video. Lottie is the exported recipe, so the page can draw it sharply at any size, play parts of it, and change its speed from code.

  2. 2

    What a file holds. The top level has v (the format version, 5.7.4 for ours), fr (frames per second), ip and op (the first and last frame), w and h (the drawing's size), assets (images or nested animations), layers and markers. Time is counted in frames: frame 45 at 30 fps is 1.5 seconds.

  3. 3

    Layers and keyframes. A shape layer has ty: 4, a nm (name), a transform ks (opacity o, rotation r, position p, anchor a, scale s) and shapes (ellipses, rectangles, paths, fills). An animated value looks like { a: 1, k: [{ t: 12, s: [92, 108], i, o }, …] }: at frame t the value is s, and i and o are the Bézier handles of the easing, the same idea as CSS cubic-bezier() (Topic 5.2). Layers listed first are drawn on top.

  4. 4

    Markers are named stretches of the timeline: { tm: 45, cm: "blink", dr: 15 } means 'blink' starts at frame 45 and lasts 15 frames. Mei sets them in the design tool and the page plays them by name (Topic 16.3). Our mascot has greet 0–45, blink 45–60, idle 60–120.

  5. 5

    **.json and .lottie.** A .lottie file (dotLottie) is a ZIP: a manifest.json plus the animations (and images, themes and state machines when present). Ours is 1,150 bytes against 6,592 for the plain JSON, because text compresses well and the file repeats itself. The older lottie-web library plays plain JSON with JavaScript drawing into SVG or canvas; the dotLottie player used here draws with a WebAssembly core into a canvas, and downloads a 1.2 MB WASM file once (Topic 16.1). We did not measure lottie-web.

  6. 6

    What a player cannot draw, it skips silently. In our test below, a layer of an unknown type, a blur effect and an expression (wiggle(2, 20)) all loaded without an error and gave exactly the same picture as the original: the feature was ignored. Only a file that is structurally not Lottie fails, and then with a real loadError. So test the exported file in the player you ship, not only in the design tool's preview.

  7. 7

    Pictures inside a Lottie are listed under assets and are usually base64 text inside the JSON, which is a third larger than the binary picture (4 characters for every 3 bytes). One embedded photo can outweigh all the vector data. Ask the designer to keep a Lottie to vectors and move pictures out.

Explain it without notes

01

What is the difference between a .json Lottie file and a .lottie file?

02

Why can a Lottie animation look different on the web from the preview in the design tool?

03

Why can a Lottie file with one embedded photo be larger than the same animation as a video?

Practice

01

Write a function markerSeconds(json) that returns each marker's name with its start and end in seconds.

02

Change the colour of the mascot's orange body (the value the red tryIt edited) to blue (#2a9df4) in the data and load it. What are the red, green and blue numbers?

Done when you can

  • You can read a Lottie file's frame rate, size, layers and markers from its JSON

  • You can turn a frame number into seconds

  • You can say what .lottie adds to .json

  • You know a player ignores unsupported features silently, and fails loudly only on a broken file