How Lottie Turns JSON Into Smooth Web Animations: A Deep Dive

This article explains how Lottie uses JSON exported from Adobe After Effects via the Bodymovin plugin to render vector animations on the web, detailing the export workflow, JSON structure, renderer initialization, layer processing, and the requestAnimationFrame‑driven playback loop with code examples.

NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
NetEase Cloud Music Tech Team
How Lottie Turns JSON Into Smooth Web Animations: A Deep Dive

Introduction

Lottie enables complex frame‑based animations created in Adobe After Effects (AE) to be exported as a JSON file via the Bodymovin extension and rendered on web, Android, iOS, or React Native using the Lottie library.

1. Implementing a Lottie Animation

Designer creates the animation in AE.

Bodymovin exports the animation as a JSON file.

Developer loads lottie-web, provides the JSON data, and initializes the animation.

import lottie from 'lottie-web';
import animationJsonData from 'xxx-demo.json'; // exported JSON

const anim = lottie.loadAnimation({
  container: document.getElementById('lottie'),
  renderer: 'svg',   // 'svg', 'canvas' or 'html'
  loop: true,
  autoplay: false,
  animationData: animationJsonData,
});

// start playback
anim.play();

Additional JSON animation templates can be found at https://lottiefiles.com/.

2. Decoding the JSON Data Format

The exported JSON contains global settings, assets, and layer definitions. w / h: canvas width and height (e.g., 200 × 200). v: Bodymovin version (e.g., 4.5.4). fr: frame rate (e.g., 30 fps). ip / op: start and end frames (e.g., 0 – 180 for a 6 s animation at 30 fps). assets: static resources such as images. layers: array of layer objects, each describing geometry, transformation, and animation data. ddd: 3‑D flag. comps: composition information.

2.1 Global Information

Lottie calculates animation time in frames, not milliseconds. For a 30 fps animation, 0 – 6 seconds corresponds to frames 0 – 180.

2.2 Layer‑Related Information

Each layer has a ty field that identifies its type (shape, image, text, etc.). In the demo there is a single shape layer ( ty = 4). Layer data is divided into three logical areas:

Content – size, position, corner radius, etc.

Transformation – anchor point, position, scale, rotation, opacity.

Keyframes – three keyframes (0, 90, 180) modify the scale property; at frame 90 the layer scales to 50 %.

2.3 Property‑Change Information

The ks object holds keyframe data. Important sub‑fields: t: keyframe time (in frames). s: start value (for 2‑D layers the third component is always 100). e: end value.

3. How Lottie Animates the JSON

3.1 Initializing the Renderer

lottie.loadAnimation

creates an AnimationItem instance, registers event listeners ( destroy, _active, _idle) and selects a renderer based on the renderer option.

function loadAnimation(params) {
  var animItem = new AnimationItem();
  setupAnimation(animItem, null);
  animItem.setParams(params);
  return animItem;
}

function setupAnimation(animItem, element) {
  animItem.addEventListener('destroy', removeElement);
  animItem.addEventListener('_active', addPlayingCount);
  animItem.addEventListener('_idle', subtractPlayingCount);
  registeredAnimations.push({ elem: element, animation: animItem });
}

The chosen renderer (SVG in most cases) inherits from BaseRenderer and implements the actual drawing logic.

3.2 Loading Static Resources

configAnimation

computes total frames, frame rate, and preloads assets (images, fonts) before drawing the first frame.

AnimationItem.prototype.configAnimation = function(animData) {
  if (!this.renderer) { return; }
  this.totalFrames = Math.floor(this.animationData.op - this.animationData.ip);
  this.firstFrame = Math.round(this.animationData.ip);
  this.renderer.configAnimation(animData);
  this.frameRate = this.animationData.fr;
  this.frameMult = this.animationData.fr / 1000;
  this.trigger('config_ready');
  this.preloadImages();
  this.loadSegments();
  this.updaFrameModifier();
  this.waitForFontsLoaded();
};

3.3 Drawing Initial Layers

initItems

calls buildAllItems, which creates a concrete element for each layer based on its ty value. The eight possible types are composition (0), solid (1), image (2), null (3), shape (4), text (5), audio (6), and camera (13).

BaseRenderer.prototype.createItem = function(layer) {
  switch (layer.ty) {
    case 0: return this.createComp(layer);
    case 1: return this.createSolid(layer);
    case 2: return this.createImage(layer);
    case 3: return this.createNull(layer);
    case 4: return this.createShape(layer);
    case 5: return this.createText(layer);
    case 6: return this.createAudio(layer);
    case 13: return this.createCamera(layer);
  }
  return this.createNull(layer);
};

In the demo the only layer is a shape, so only createShape runs.

3.4 Animation Playback

Calling play triggers the _active event, which starts a requestAnimationFrame loop.

AnimationItem.prototype.play = function(name) {
  this.trigger('_active');
};

function activate() {
  window.requestAnimationFrame(first);
}

The first function records the start time and schedules resume on the next frame. resume calculates elapsed time, converts it to a frame increment using frameModifier = fr / 1000, updates the current raw frame, and renders the frame.

function resume(nowTime) {
  var elapsed = nowTime - initTime;
  var next = this.currentRawFrame + elapsed * this.frameModifier;
  this.setCurrentRawFrameValue(next);
  initTime = nowTime;
  if (playingAnimationsNum && !_isFrozen) {
    window.requestAnimationFrame(resume);
  }
}

AnimationItem.prototype.setCurrentRawFrameValue = function(value) {
  this.currentRawFrame = value;
  this.renderFrame();
};

During each render, TransformPropertyFactory interpolates keyframe values (scale, rotation, position, opacity) and produces a CSS matrix that is applied to the DOM element. Because requestAnimationFrame runs at ~60 fps, Lottie can compute sub‑frame values for a smoother 30 fps animation.

4. Advantages and Limitations

Designers work entirely in AE; developers render the exported JSON without recreating the animation.

SVG output scales without loss of quality.

JSON files are reusable across Web, Android, iOS, and React Native.

JSON size is typically much smaller than GIF or APNG, improving performance.

Lottie‑web library size is relatively large (≈513 KB uncompressed, 144 KB minified, 39 KB gzipped).

Exporting each frame as an image inflates the JSON size.

Some AE effects are unsupported or have performance constraints.

5. References

https://github.com/airbnb/lottie-web/

http://airbnb.io/lottie/#/

https://www.zhangxinxu.com/wordpress/2012/06/css3-transform-matrix-%E7%9F%A9%E9%98%B5/

https://developer.mozilla.org/en-US/docs/Web/API/Window/requestAnimationFrame

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

frontendLottieSVGrequestAnimationFrameWeb animationBodymovinJSON animation
NetEase Cloud Music Tech Team
Written by

NetEase Cloud Music Tech Team

Official account of NetEase Cloud Music Tech Team

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.