本文作者:ivweb caorich

data.json 文件格式

以下的内容当设计接口的详细说明时,请移步bodymovin的官方文档。文档的是用JSON Schema编写的,这玩意儿就是一个词汇表,相当于本体的json实现,规范了对象、属性的表达方式(而已)。

言归正传。了解AE导出的data.json数据格式的最好方法就是先制作一个简单得不能再简单的关键帧动画,看看它导出的data.json是什么样的。

我们用AE制作了一个简单的动画,一个宽100,高200的长方形,在400*400的正方形白色底的画布上,从位置(100,200)移动到(300,200)。动画的时间长度为1s, fpr为30(一秒30帧)。现在我们导出data.json文件来看看

 {
    "v": "4.6.7",   // 版本号
    "fr": 30,     // 帧率 Frame Rate,也就是fps,每秒帧数,这里是每秒30帧数
    "ip": 0,      // 开始帧 in point
    "op": 30,     // 结束帧 out point
    "w": 400,    // 画布宽度
    "h": 400,    // 画布高度
    "nm": "testComp",    // 合成的名称。AE中将多个图层的层叠叫做“合成”
    "ddd": 0,     // 未知
    "assets": [],    // 引用资源列表
    "layers": [...]   // 图层的信息,其元素是
}

再看layer属性:

 "layers": [{
    "ddd": 0,   // is 3d layer? boolean
    "ind": 1,    // index
    "ty": 4,    // type {1: solid, 2:image, 3: null, 4: shape, 5: text}
    "nm": "Shape Layer 1",   // name
    "ks": {...},   // transform 用于描述变换规则
    "ao": 0,     // auto orient,布尔值
    "shapes": [{   //图层中的shape列表
        "ty": "gr",   // type: 这里是gr指group
        "it": [{...}],   
        "nm": "Rectangle 1",   //name
        "np": 3,     // number of properties. 属性的数量
        "cix": 2,    // 未知
        "ix": 1,     // 未知
        "mn": "ADBE Vector Group"   //Match Name, 在AE中对应的类型名称
    }],
    "ip": 0,    // in point
    "op": 30,  //out point
    "st": 0,  //start time, start time of this layer
    "bm": 0,   // blend mode
    "sr": 1   // stretch, layer time stretching
}]

layers是一个数组对象,本文的例子只有一个图层,所以layers中只有一个元素。在指明一些基本信息之余,关键看ks属性和shapes属性。ks属性定义了该图层的变换规则。而shapes属性定义了图层中各个形状(shape)。在本文的例子中,只包含一个图层,该图层包含一个形状:“adbe Vector Group”,即我们画的矩形。 先看看ks属性中的内容:

 "ks": { 
    "o": {      // opacity
        "a": 0,   // 布尔值,判断这个属性是否有动画
        "k": 100   // value
    },
    "r": {   // Rotation
        "a": 0,
        "k": 0
    },
    "p": {   // Position
        "a": 1,   // 有动画
        "k": [{   // 如果有动画,那么这个值就是一个‘multiDimensionalKeyframed’
            "i": {   // Bezier curve interpolation in value. 
                "x": 0.833,
                "y": 0.833
            },
            "o": {   // Bezier curve interpolation out value.
                "x": 0.167,
                "y": 0.167
            },
            "n": "0p833_0p833_0p167_0p167",
            "t": 0,  // time
            "s": [100, 200, 0],  // start value
            "e": [300, 200, 0],  // end value
            "to": [31.4798240661621, 0, 0],
            "ti": [-41.3984909057617, 0, 0]
        }, {
            "i": {
                "x": 0.833,
                "y": 0.833
            },
            "o": {
                "x": 0.167,
                "y": 0.167
            },
            "n": "0p833_0p833_0p167_0p167",
            "t": 30,   
            "s": [300, 200, 0],
            "e": [300, 200, 0],
            "to": [2.43751406669617, 0, 0],
            "ti": [-1.8535099029541, 0, 0]
        }, {
            "t": 31
        }]
    },
    "a": {  // Anchor Point 变换的锚点
        "a": 0,
        "k": [0, 0, 0]   // 本例中动画的锚点在中心
    },
    "s": {  // scale
        "a": 0,
        "k": [100, 100, 100]
    }
},

ks属性描述关键帧信息。从上面的例子可以看到,矩形从第0帧的[100, 200, 0]位置,移动到了第30帧的[300, 200, 0]位置。很明显的关键帧表述。关于关键帧的表述第二小节详细讲述。

然后我们来看看shapes属性中的内容。shapes是一个数组,里面放着各种shape。每个shape由相关组件(item)组成,例如本例中,一个圆角矩形的shape由四个部分组成

  • round corner
  • stroke
  • fill
  • transform

round corner定义了外形,stroke定义了边框,fill定义了填充,transform定义了变换。

 "it": [{
    "ty": "rc",   // type = round corner 圆角矩形
    "d": 1,
    "s": {   // size
        "a": 0,
        "k": [100, 150]
    },
    "p": {   // position
        "a": 0,
        "k": [0, 0]
    },
    "r": {   // roundness
        "a": 0,
        "k": 10
    },
    "nm": "Rectangle Path 1",
    "mn": "ADBE Vector Shape - Rect"
}, {
    "ty": "st",   // type: stroke
    "c": {   // color
        "a": 0,
        "k": [1, 1, 1, 1]
    },
    "o": {   // opacity
        "a": 0,
        "k": 100
    },
    "w": {  // width: stroke width
        "a": 0,
        "k": 0
    },
    "lc": 1,   //line cap: {1:butt, 2:round, 3: mint}   
    "lj": 1,   // line join: {1: miter, 2: round, 3: butt}
    "ml": 4,   // miter limit.  
    "nm": "Stroke 1",
    "mn": "ADBE Vector Graphic - Stroke"
}, {
    "ty": "fl",   // fill
    "c": {   // color
        "a": 0,
        "k": [1, 0, 0, 1]
    },
    "o": {   // opacity
        "a": 0,
        "k": 100
    },
    "r": 1,   // 文档中没有r,应该是没用的
    "nm": "Fill 1",
    "mn": "ADBE Vector Graphic - Fill"
}, {
    "ty": "tr",   // transform
    "p": {    // position
        "a": 0,
        "k": [0, 0],
        "ix": 2  // index
    },
    "a": {   // anchor point
        "a": 0,
        "k": [0, 0],
        "ix": 1
    },
    "s": {   // scale
        "a": 0,
        "k": [100, 100],
        "ix": 3
    },
    "r": {   // rotate
        "a": 0,
        "k": 0,
        "ix": 6
    },
    "o": {   // opacity
        "a": 0,
        "k": 100,
        "ix": 7
    },
    "sk": {   // skew
        "a": 0,
        "k": 0,
        "ix": 4
    },
    "sa": {    // skew axis
        "a": 0,
        "k": 0,
        "ix": 5
    },
    "nm": "Transform"
}],

化繁为简

如果你没时间完整读完整读完上面的json文件接口分析。这里给出了一个简单介绍。data.json是对AE文件的完全(虽然很多高级功能不支持)描述。AE文件中将一个合成描述为多个layer,每个layer上有很多shape,每个shape可以由多个shape组成。shape可以自定义变换,整个layer也可以自定义变换。

可以用下图来表示

再用一个简化的json来表示就是:

var comp = {
    layers:[{
        name: 'layer1',
        shapes: [{
            name: 'shape1',
            stroke: {...},
            fill: {...},
            transform: {
                scale: {...},
                translate: {...},
                rotate: {...},
                skew: {...}
            }
        },{
            ...
        }]
    },{
        ...
    }]
}

看到scale\translate\rotate属性,是不是感觉和css的动画就很像了~

bodymovin.js源码分析

bodymovin的源码其实很好看懂。如果略去复杂的矩阵运算和关键帧的运算过程。bodymovin其实就三大块儿:

  • AnimationItem
  • Renderer
  • AnimationElements

AnimationItem是接口的汇总。任何用bodymovin播放的动画都会抽象成一个AnimationItem。渲染动画时,AnimationItem调用renderFrame方法,将渲染过程移交给Renderer,后者根据data.jsonlayers生成不同的动画单元ElementsElements进行渲染工作。基本的构建见下图。

不同的Element的渲染方法,细化下来,都是一些基本的渲染方法。例如,以下是ShapeELement的render方法:

CVShapeElement.prototype.renderShape = function (parentTransform, items, data, isMain) {
    // ...

    if (items[i].ty == 'sh' || items[i].ty == 'el' || items[i].ty == 'rc' || items[i].ty == 'sr') {  // 绘制路径
            this.renderPath(items[i], data[i], groupTransform);
        } else if (items[i].ty == 'fl') {  //渲染fill
            this.renderFill(items[i], data[i], groupTransform);
        } else if (items[i].ty == 'st') {  //渲染stroke
            this.renderStroke(items[i], data[i], groupTransform);
        } else if (items[i].ty == 'gr') {  //渲染子shape
            this.renderShape(groupTransform, items[i].it, data[i].it);
        } else if (items[i].ty == 'tm') {
            //
        }
}

二次开发

那么,基于上面的分析:

bodymovin.js写好了复杂的Element类,等于向我们提供了一个强大的渲染引擎。在接口层AnimationItemRenderer类中,我们可以添加自己的方法来最大程度的发挥bodymovin的作用为己所用。

目前,bodymovin已经为我们提供了方便的接口。

bodymovin.js的减包实践

基本思路

bodymovin同时支持canvas、h5、svg的方式渲染data.json。对于我们日常的动画需求,当遇到需要运用bodymovin来实现的动画效果,势必都是难以用原生css3完成的,所以,让bodymovin来支持h5的渲染没有必要。另外,svg涉及大量的dom操作,其效率比canvas低。

所以,对于减包操作,我首先想到的是将h5和svg的渲染代码砍掉,这样应该能减少不少代码量。

减包后的bodymovin我称作bodymoon,压缩后代码从240K减小到160K。

实践

最近在写一些顶层接口,使得bodymoon使用起来更加方便。

原文链接:http://www.ivweb.io/topic/592864df09439b0640aefbb9

文章来源于腾讯云开发者社区,点击查看原文