lottie对一系列框架的总称,其中包括lottie-web(bodymovin)、lottie-android、lottie-ios以及lottie-rn。lottie的工作流如下图展示。

  1. 设计师通过Adobe AE设计动画(不能使用全开的效果,需要克制),具体可以参考https://yuque.antfin-inc.com/paradise/best-practice/lottie-rules
  2. 通过bodymovin插件导出data.json
  3. 应用加载data.json,展示动画。不同的平台支持的特性是不一样的,具体可以参考http://airbnb.io/lottie/supported-features.html

image.png

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