Image of Banner: aescripts.com/bodymovin/

This article first analyzes Lottie – Web animation implementation principle, if you have seen the students can directly read my other animation articles:

  • Web frame animation solution – APNG principle and implementation
  • Web Frame animation solution – Lottie – Web source code anatomy
  • Web Frame animation solution – WebGl implements transparent video rendering

preface

Lottie is a solution for complex frame animation that provides a tool flow from designers using After Effects to developers implementing animations at all ends. After completing the animation through AE, the designer can export a JSON format animation data using AE extension program Bodymovin, and then the developers can render the generated JSON data into animation through Lottie.

1. How to implement a Lottie animation

  1. Designers use AE to create animations.
  2. Export the animation to JSON data files using Lottie’s Bodymovin plugin.
  3. Load the Lottie library with the JSON file and the following lines of code to implement a Lottie animation.
import lottie from 'lottie-web';
import animationJsonData from 'xxx-demo.json';  / / the json file

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

// Start animation
lot.play();
Copy the code

For more animation JSON templates, see Lottiefiles.com/

2. Interpret the data format of JSON files

I made my own Lottie Demo -> point me preview

  • 0 s to 3 s,scaleAttribute value changed from 100% to 50%.
  • 3 s to 6 s,scaleProperty value changed from 50% to 100%, complete animation.

The JSON data structure exported by Bodymovin plug-in is shown in the figure below:

You can view the detailed JSON information in the Demo. The JSON information is simply named and may be difficult to understand at first viewing. Next, the author makes his own Demo for interpretation.

2.1 Global Information

On the left is the information that needs to be filled in for new animation composition using AE, corresponding to the first JSON information on the right is as follows:

  • whB: 200 wide and 200 high
  • v: Bodymovin plugin version 4.5.4
  • fr:Frame rate 30fps
  • ipop: Start frame 0, end frame 180
  • assets: Static resource information (such as images)
  • layersLayer information (each layer in the animation and the action information)
  • ddd: Indicates whether it is 3D
  • comps: Composite layer

Fr, IP and OP are particularly important in Lottie animation. As mentioned above, our animation Demo is 0-6s, but Lottie calculates animation time based on frame rate. The Demo set the frame rate to 30fps, so 0-6s equals 0-180 frames.

2.2 Layer related information

After understanding the outer information of JSON, we will expand the detailed information of layers in JSON. First, the animation details of demo are as follows:

There are three main areas:

  • The content area contains information about the size, position, roundness of the shape layer.
  • The change area contains five change properties (anchor point, position, scale, rotation, opacity).
  • Zoom 3 frames (the green area in the image), modify the zoom properties at frame 0, 90 and 180, as shown in the image at frame 90, and scale the layer to 50%.

Corresponding to the animation information in the figure above, we can correspond to the layers in JSON. As shown below:

2.3 Attribute change information

Next, look at the S-expansion in KS (variable properties), which is the scaling information.

Among them:

  • tRepresents the number of key frames
  • sRepresents before the change (the layer is two-dimensional, so the third value is fixed at 100).
  • eRepresents the change (the layer is two-dimensional, so the third value is fixed at 100).

3. How does Lottie animate JSON data

Now that you’ve looked at what JSON data means, how does Lottie make JSON data work? Next, I will read the Lottie source code in combination with Demo. I will only show part of the source code. The key is to clarify the thinking, and do not stick to the source code.

The following source code introduction is mainly divided into 2 parts:

  • Animation Initialization (section 3.1-3.3)
  • Animation Playback (Section 3.4)

3.1 Initializing the renderer

As shown in the Demo, Lottie initializes the animation using the loadAnimation method. The renderer initialization process is as follows:

function loadAnimation(params){
    // Generate the current animation instance
    var animItem = new AnimationItem();
    // Register the animation
    setupAnimation(animItem, null);
    // Initialize the animation instance parameters
    animItem.setParams(params);
    return animItem;
}

function setupAnimation(animItem, element) {
    // Listen on events
    animItem.addEventListener('destroy', removeElement);
    animItem.addEventListener('_active', addPlayingCount);
    animItem.addEventListener('_idle', subtractPlayingCount);
    // Register the animation
    registeredAnimations.push({elem: element, animation:animItem});
    len += 1;
}
Copy the code
  • AnimationItemThis class is the base class for Lottie animation,loadAnimationThe method will be a misterAnimationItemInstance and return, used by the developerConfigure parameters and methodsIt’s all coming out of this class.
  • generateanimItemAfter instance, callsetupAnimationMethods. This method listens firstdestroy,_active,_idleThree events are waiting to be triggered. Since multiple animations can run in parallel, global variables are definedlen,registeredAnimationsTo determine and cache registered instances of animation.
  • Next callanimItemThe instancesetParamsMethod to initialize animation parameters, except for initializationloopautoplayAnd other parameters, the most important is to choose the renderer. As follows:
AnimationItem.prototype.setParams = function(params) {
    // Select the renderer according to the developer configuration
    switch(animType) {
        case 'canvas':
            this.renderer = new CanvasRenderer(this, params.rendererSettings);
            break;
        case 'svg':
            this.renderer = new SVGRenderer(this, params.rendererSettings);
            break;
        default:
            / / HTML type
            this.renderer = new HybridRenderer(this, params.rendererSettings);
            break;
    }

    // Render initialization parameters
    if (params.animationData) {
        this.configAnimation(params.animationData); }}Copy the code

Lottie provides SVG, Canvas, and HTML rendering modes, and generally uses either the first or the second.

  • The SVG renderer supports the most features and uses the most rendering methods. And SVG is scalable, with no distortion at any resolution.
  • Canvas renderer is to continuously redraw the object of each frame according to the data of animation.
  • HTML renderers are limited by their capabilities and support minimal features. They can only do very simple graphics or text and do not support filters.

Each renderer has its own implementation and complexity, but the more complex the animation, the higher the performance cost, depending on the actual situation. Renderers in the player/js/renderers/ folder, this Demo will only analyze the implementation of SVG rendering animation. Since all three renderers are based on the BaseRenderer class, methods of the BaseRenderer class will appear in addition to SVGRenderer below.

3.2 Initialize animation properties and load static resources

After confirming that the SVG renderer is in use, the configAnimation method is called to initialize the renderer.

AnimationItem.prototype.configAnimation = function (animData) {
    if(!this.renderer) {
        return;
    }
    
    / / the total number of frames
    this.totalFrames = Math.floor(this.animationData.op - this.animationData.ip);
    this.firstFrame = Math.round(this.animationData.ip);
    
    // Render initialization parameters
    this.renderer.configAnimation(animData);

    / / frame rate
    this.frameRate = this.animationData.fr;
    this.frameMult = this.animationData.fr / 1000;
    this.trigger('config_ready');
    
    // Load a static resource
    this.preloadImages();
    this.loadSegments();
    this.updaFrameModifier();
    
    // Wait for static resources to finish loading
    this.waitForFontsLoaded();
};
Copy the code

This method initializes more properties of the animation object, such as totalFrames, frame rate frameMult, and so on. Then load some other resources, such as images, fonts, etc. As shown below:

At the same time, the static resources are loaded in the waitForFontsLoaded method. After loading, the animation layer is drawn by calling the initItems method of the SVG renderer.

AnimationItem.prototype.waitForFontsLoaded = function(){
    if(!this.renderer) {
        return;
    }
    // Check that the load is complete
    this.checkLoaded();
}

AnimationItem.prototype.checkLoaded = function () {
    this.isLoaded = true;

    // Initialize all elements
    this.renderer.initItems();
    setTimeout(function() {
        this.trigger('DOMLoaded');
    }.bind(this), 0);

    Render the first frame
    this.gotoFrame();
    
    // Auto play
    if(this.autoplay){
        this.play(); }};Copy the code

As you can see from the checkLoaded method, after initItems initializes all elements, the first frame is rendered by gotoFrame. If the developer has set autoPlay to true, the play method is called directly. It’s good to have an impression here, and we’ll talk about that later. Let’s look at the initItems implementation details first.

3.3 Draw the initial animation layer

The initItems method basically calls buildAllItems to create all the layers. BuildItem calls createItem to determine the layer type. BuildItem calls createItem to determine the layer type.

When animating, designers manipulate layer elements such as images, shapes, text, and so on. So each layer in layers will have a field ty to distinguish it. Combined with the createItem method, there are eight types.

BaseRenderer.prototype.createItem = function(layer) {
    // Create an instance of the corresponding SVG element class according to the layer type
    switch(layer.ty){
        case 0:
            / / synthetic
            return this.createComp(layer);
        case 1:
            / / solid
            return this.createSolid(layer);
        case 2:
            / / picture
            return this.createImage(layer);
        case 3:
            // Empty element
            return this.createNull(layer);
        case 4:
            / / shape
            return this.createShape(layer);
        case 5:
            / / text
            return this.createText(layer);
        case 6:
            / / audio
            return this.createAudio(layer);
        case 13:
            / / the camera
            return this.createCamera(layer);
    }
    return this.createNull(layer);
};
Copy the code

Since I, and most developers, are not professional AE players, you don’t have to worry about what each type is, just sort out the main ideas. Based on the author’s Demo, there is only one layer and the ty of the layer is 4. Is a Shape layer, so only the createShape method is executed during layer initialization.

The rendering logic of other layer types, such as Image, Text, Audio, etc., is implemented in the source code of player/js/elements/ folder. The specific implementation logic is not expanded here.

The next step is to initialize the element-related attributes by executing the createShape method.

In addition to some detailed initialization methods, one of the notable ones is the initTransform method.

initTransform: function() {
    this.finalTransform = {
        mProp: this.data.ks
            ? TransformPropertyFactory.getTransformProperty(this.this.data.ks, this)
            : {o:0},
        _matMdf: false._opMdf: false.mat: new Matrix()
    };
},
Copy the code

TransformPropertyFactory is used to initialize the transform. In combination with frame 0 of Demo, the transform is initialized as follows:

  • Opacity 100%
  • Zoom 100%
transform: scale(1);
opacity: 1;
Copy the code

Why do you need to initialize transform and opacity when initializing a render layer? This question is answered in section 3.4.

3.4 Lottie animation playback

Before analyzing Lottie source animation playback, let’s recall. The author’s Demo animation Settings:

  • 0 s to 3 s,scaleAttribute value changed from 100% to 50%.
  • 3 s to 6 s,scaleAttribute value changed from 50% to 100%.

If you follow this setup, 3s change once, the animation will be too stiff. So the designers set the frame rate at 30fps, which means that it changes every 33.3ms so that the animation doesn’t get too stiff. How to implement this change is described in Section 3.3: transform and opacity.

The five change properties mentioned in section 2.2 (anchor point, position, scale, rotation, opacity). Opacity is controlled by CSS opacity, while the other four (anchor point, position, zoom and rotation) are controlled by transform matrix. The actual initial values in the author’s Demo are as follows:

transform: matrix(1.0.0.1.100.100);
/* Transform: scale(1); Just for ease of understanding */
opacity: 1;
Copy the code

This is because properties such as rotation and scaling are essentially implemented using the Matrix () method of Transform, so Lottie uses matrix processing uniformly. Transform: Scale is often used by developers simply because it’s easier to understand, remember and learn. You can learn from Zhang Xinxu’s understanding of matrix in CSS3 Transform.

Therefore, the Lottie animation playback process can be summarized as follows:

  1. Render the layer and initialize all layerstransformopacity
  2. According to the frame rate of 30fps, calculate the corresponding of each frame (every 33.3ms)transformopacityAnd modify the DOM

But how does Lottie control the 30-fps interval? What if the designer sets 20FPS or 40FPS? Can setTimeout and setInterval be implemented? Take a look at how the source code handles this problem and how to implement a common solution.

Lottie animations are played primarily using the Play method of the AnimationItem instance. If the developer configures autoPlay to true, the play method will be called directly after all initialization is complete (as mentioned in section 3.2). Otherwise, the developer calls the play method on its own initiative.

Let’s see the details of the play process from the play method:

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

Without the extra code, the play method basically raises the _active event that was registered when section 3.1 was initialized.

animItem.addEventListener('_active', addPlayingCount);

function addPlayingCount(){
    activate();
}

function activate(){
    // Trigger the first frame render
    window.requestAnimationFrame(first);
}
Copy the code

The animation is controlled by calling the requestAnimationFrame method and the resume method over and over again.

function first(nowTime){
    initTime = nowTime;
    // requestAnimationFrame evaluates the DOM each time
    window.requestAnimationFrame(resume);
}
Copy the code

The animation parameters mentioned above:

  • The start frame is 0
  • The end frame is 180
  • The frame rate is 30 FPS

RequestAnimationFrame can normally reach 60 FPS (about every 16.7ms). So how does Lottie ensure that the animation runs smoothly at 30 FPS (every 33.3ms)? The designer wants to calculate the changes every 33.3ms. This can also be done with requestAnimationFrame every 16.7ms. It can also calculate the changes of the animation. But the calculation is more detailed, but also make the animation more smooth, so whether 20fps or 40fps can be handled, take a look at the source code is how to deal with.

The main logic in the resume method is as follows:

function resume(nowTime) {
    // The interval between two RequestAnimationFrames
    var elapsedTime = nowTime - initTime;

    // Next computation frames = last execution frames + interval frames
    // frameModifier is the frame rate (fr / 1000 = 0.03)
    var nextValue = this.currentRawFrame + value * this.frameModifier;
    
    this.setCurrentRawFrameValue(nextValue);
    
    initTime = nowTime;
    if(playingAnimationsNum && ! _isFrozen) {window.requestAnimationFrame(resume);
    } else {
        _stopped = true;
    }
}

AnimationItem.prototype.setCurrentRawFrameValue = function(value){
    this.currentRawFrame = value;
    // Render the current frame
    this.renderFrame();
};
Copy the code

Resume:

  • The current time and the last time will be calculated firstdiffTime.
  • The current number of frames from the start of the animation to the present is then calculated. Note that the number of frames is only a unit of calculation relative to the AE Settings, and can have decimals.
  • At last,renderFrame()Method updates the DOM change corresponding to the current frame.

For example:

RequestAnimationFrame interval = 16.78ms

Current frame count: 70.25 + 16.78 * 0.03 = 70.7534 framesCopy the code

Since 70.7534 frames are in the 0-90 frame animation range in the Demo, the frame ratio (representing the percentage of animation run time) is calculated as follows:

Frame ratio: 70.7534/90 = 0.786148889Copy the code

For 0-90 frames, scale the layer from 100% to 50%. Since only 50% of the changes are calculated, scale to the following:

Zoom ratio: 100 - (50 * 0.781666) = 60.69255555%Copy the code

The corresponding calculation code is in the TransformPropertyFactory class:

// Calculate the percentage
perc = fnc((frameNum - keyTime) / (nextKeyTime - keyTime ));
endValue = nextKeyData.s || keyData.e;
/ / calculated value
keyValue = keyData.s[i] + (endValue[i] - keyData.s[i]) * perc;
Copy the code

FNC is the calculation function. If bezier motion curve function is set, FNC will modify the calculation rules accordingly. The current Demo is linear for ease of understanding. Specific source code interested students can view.

After calculating the current scale value, use TransformPropertyFactory to calculate the matrix value of the current corresponding transform, and then modify the CSS properties on the corresponding DOM element. In this way, the requestAnimationFrame continuously calculates the number of frames, and then calculates the corresponding CSS changes, in a certain amount of time, to achieve the animation. The playback process is as follows:

It’s important to keep in mind that Lottie uses the frames set as a unit of account. Lottie does not make every change based on the designer’s 30fps (every 33.3ms). Instead, more detailed changes are calculated based on the requestAnimationFrame interval (about every 16.7ms) to keep the animation running smoothly.

SetTimeout and setInterval are not implemented, because they both have their own disadvantages. I will not expand them here, and you can check the information by yourself. RequestAnimationFrame uses a system time interval to maintain optimal drawing efficiency and provide a unified refresh mechanism for animations to save system resources, improve system performance, and improve visual effects.

4, summarize

Although we understand the implementation principle of Lottie, there are also some advantages and disadvantages in practical application, which should be made according to the actual situation.

4.1 Advantages of Lottie

  1. Designers through AE animation, the front end can be directly restored, there will be no buyers show sellers show.
  2. SVG is scalable and does not distort at any resolution.
  3. JSON files that can be reused by multiple applications (Web, Android, iOS, and React Native).
  4. JSON files are much smaller than GIF and APNG files, and have better performance.

4.2 Deficiencies of Lottie

  1. The Lottie-Web file itself is still large at 513K uncompressed, 144K compressed in the light version, and 39K after Gzip. So, be aware of lottie-Web loading.
  2. Unnecessary sequence frames. The main idea of Lottie animation is to draw a layer and constantly change the CSS properties. If the designer is lazy and uses some plug-ins to achieve the animation effect, each frame may be a picture, as shown in the following picture, which will cause the JSON file to be very large. Please communicate with the designer in advance.

  1. Some effects are not supported. There are a small number of AE animation effects that Lottie cannot realize, some of which are due to performance problems and some of which are not done. Please pay attention to the communication with the designer in advance and click me to check.

5. Reference materials

  • Github.com/airbnb/lott…
  • airbnb.io/lottie/#/
  • Understand Matrix in CSS3 transform
  • window.requestAnimationFrame