Photo credit: aescripts.com/bodymovin/
Author: Qing Zhou
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
- Designers use AE to create animations.
- Export the animation to JSON data files using Lottie’s Bodymovin plugin.
- 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,
scale
Attribute value changed from 100% to 50%. - 3 s to 6 s,
scale
Property 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:
w
和h
B: 200 wide and 200 highv
: Bodymovin plugin version 4.5.4fr
:Frame rate 30fpsip
和op
: Start frame 0, end frame 180assets
: Static resource information (such as images)layers
Layer information (each layer in the animation and the action information)ddd
: Indicates whether it is 3Dcomps
: 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:
t
Represents the number of key framess
Represents before the change (the layer is two-dimensional, so the third value is fixed at 100).e
Represents 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
-
The AnimationItem class is the base class for Lottie animation. The loadAnimation method returns an instance of AnimationItem. The configuration parameters and methods used by the developer come from this class.
-
After the animItem instance is generated, the setupAnimation method is called. This method first listens for destroy, _active, and _idle events to be triggered. Since multiple animations can be performed in parallel, global variables len, registeredAnimations, and so on are defined to determine and cache registered instances of animations.
-
The setParams method of the animItem instance is then called to initialize the animation parameters. In addition to initializing parameters such as loop, autoplay, and most importantly, selecting 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,
scale
Attribute value changed from 100% to 50%. - 3 s to 6 s,
scale
Attribute 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:
- Render the layer and initialize all layers
transform
和opacity
- According to the frame rate of 30fps, calculate the corresponding of each frame (every 33.3ms)
transform
和opacity
And 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 diff time for the current time and the last time is first calculated.
-
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.
-
Finally, the DOM changes corresponding to the current frame are updated with renderFrame().
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
- Designers through AE animation, the front end can be directly restored, there will be no buyers show sellers show.
- SVG is scalable and does not distort at any resolution.
- JSON files that can be reused by multiple applications (Web, Android, iOS, and React Native).
- JSON files are much smaller than GIF and APNG files, and have better performance.
4.2 Deficiencies of Lottie
- 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.
- 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.
- 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
This article is published from netease Cloud Music big front end team, the article is prohibited to be reproduced in any form without authorization. Grp.music – Fe (at) Corp.Netease.com We recruit front-end, iOS and Android all year long. If you are ready to change your job and you like cloud music, join us!