lottie json文件剖析
lottie对一系列框架的总称,其中包括lottie-web(bodymovin)、lottie-android、lottie-ios以及lottie-rn。lottie的工作流如下图展示。
- 设计师通过Adobe AE设计动画(不能使用全开的效果,需要克制),具体可以参考https://yuque.antfin-inc.com/paradise/best-practice/lottie-rules
- 通过bodymovin插件导出data.json
- 应用加载data.json,展示动画。不同的平台支持的特性是不一样的,具体可以参考http://airbnb.io/lottie/supported-features.html
lottie最核心的就是这个data.json了,这个json相当于是一个协议,不同的平台分别是实现这个协议来完成对AE做出来的动画的支持。lottie-web已经对data.json的大部分字段有了说明,可以说非常的复杂了。请参考https://github.com/airbnb/lottie-web/tree/master/docs/json。
lottie的启动过程
从上面的流程图中可以知道lottie-web加载启动流程并不复杂,但是其支持三种渲染模式,分别是SVG、Canvas和HTML,真正复杂的是各种animation的效果在这三个渲染引擎上的实现。
Animation
启动流程并不算复杂,启动完毕之后,就需要根据加载的json数据去更新界面显示来实现动画。AE中的动画基本以下几部分组成。
- layers:复合层列表
- shape:形状图层
- solid:单色图层
- comp:复合图层
- image:图像图层
- null:空图层
- text:文本图层
- assets:资源列表,当前只包含Comp和Images
- comp:复合图层
- image:图像图层
- chars:文本层的字符
- effects:各种效果
image
{
"id": "image_0", // 图像id
"w": 390, // 图像宽度
"h": 261, // 图像高度
"u": "", // 图像地址
"p": "image_0-4bc92.png", // 图像名字
"e": 0
}
lottie-web中解析图像相关的代码如下:
// AnimationItem
AnimationItem.prototype.getAssetsPath = function (assetData) {
var path = '';
if(assetData.e) {
path = assetData.p;
} else if(this.assetsPath){
var imagePath = assetData.p;
if(imagePath.indexOf('images/') !== -1){
imagePath = imagePath.split('/')[1];
}
path = this.assetsPath + imagePath;
} else {
path = this.path;
path += assetData.u ? assetData.u : '';
path += assetData.p;
}
return path;
};
shape
形状分为很多种,包括形状、椭圆、矩形、圆形等等,每个形状的参数也不一样。下面以Stroke为例。
{
"ty": "st", // 这里指定shape的类型,不同的shape类型,下面的参数是不一样的。
"c": { // color
"a": 0,
"k": [
0.917647123337,
0.47058826685,
0.474509835243,
1
],
"ix": 3
},
"o": { // opacity
"a": 0,
"k": 100,
"ix": 4
},
"w": { // width
"a": 0,
"k": 13,
"ix": 5
},
"lc": 2, // line Cap
"lj": 1, // line join
"ml": 10, // minter limit
"nm": "Stroke 1", // ae name
"mn": "ADBE Vector Graphic - Stroke", // ae match name
"hd": false
}
在lottie-web中,SVGRenderer对shape的处理函数如下
function createRenderFunction(data) {
var ty = data.ty;
switch(data.ty) {
case 'fl':
return renderFill;
case 'gf':
return renderGradient;
case 'gs':
return renderGradientStroke;
case 'st':
return renderStroke;
case 'sh':
case 'el':
case 'rc':
case 'sr':
return renderPath;
case 'tr':
return renderContentTransform;
}
}
CanvasRenderer对shape的处理函数如下:
CVShapeElement.prototype.renderShape = function(parentTransform,items,data,isMain){
var i, len = items.length - 1;
var groupTransform;
groupTransform = parentTransform;
for(i=len;i>=0;i-=1){
if(items[i].ty == 'tr'){
groupTransform = data[i].transform;
this.renderShapeTransform(parentTransform, groupTransform);
}else if(items[i].ty == 'sh' || items[i].ty == 'el' || items[i].ty == 'rc' || items[i].ty == 'sr'){
this.renderPath(items[i],data[i]);
}else if(items[i].ty == 'fl'){
this.renderFill(items[i],data[i],groupTransform);
}else if(items[i].ty == 'st'){
this.renderStroke(items[i],data[i],groupTransform);
}else if(items[i].ty == 'gf' || items[i].ty == 'gs'){
this.renderGradientFill(items[i],data[i],groupTransform);
}else if(items[i].ty == 'gr'){
this.renderShape(groupTransform,items[i].it,data[i].it);
}else if(items[i].ty == 'tm'){
//
}
}
if(isMain){
this.drawLayer();
}
};
solid
CanvasRenderer对Solid部分的实现
CVSolidElement.prototype.renderInnerContent = function() {
var ctx = this.canvasContext;
ctx.fillStyle = this.data.sc;
ctx.fillRect(0, 0, this.data.sw, this.data.sh);
//
};
HybridRenderer对solid的实现
HSolidElement.prototype.createContent = function(){
var rect;
if(this.data.hasMask){
rect = createNS('rect');
rect.setAttribute('width',this.data.sw);
rect.setAttribute('height',this.data.sh);
rect.setAttribute('fill',this.data.sc);
this.svgElement.setAttribute('width',this.data.sw);
this.svgElement.setAttribute('height',this.data.sh);
} else {
rect = createTag('div');
rect.style.width = this.data.sw + 'px';
rect.style.height = this.data.sh + 'px';
rect.style.backgroundColor = this.data.sc;
}
this.layerElement.appendChild(rect);
};
SVGRenderer对solid的实现
ISolidElement.prototype.createContent = function(){
var rect = createNS('rect');
rect.setAttribute('width',this.data.sw);
rect.setAttribute('height',this.data.sh);
rect.setAttribute('fill',this.data.sc);
this.layerElement.appendChild(rect);
};
comp
null
text
effects
原理和上面是一样的,暂时忽略
剩下的都是磨细节和对AE一些概念的熟悉了解了,不能速成。
https://yuque.antfin-inc.com/paradise/share/base-lottie
备注
svg使用:https://codepen.io/fragno/pen/XObZgG
canvas使用:2d && 3d
lottiedemo: https://codepen.io/airnan/pen/JmOqbN