lottie-web系列之一:如何看懂json参数(图片图层

12,139 阅读14分钟

最近项目中使用了lottie-web,但是lottie-web生成使用的json特别的长,那我们在开发中要修改它的参数,就必然要看懂它每个字段代表的意义。现在将项目中用到的知识总结下来,希望对有用它的小伙伴有所帮助。

什么是lottie-web?

Lottie是一个用于Web和iOS的移动库,它可以使用Bodymovin(一个AE插件)解析以json格式导出的Adobe After Effects动画,并在移动设备上进行原生渲染!。做动画的时候,让设计同学先做好动画,导出json给开发同学,而开发同学只要拿着这一份json使用lottie进行解析,就可以渲染出动画了。

如何使用lottie

  • 引入脚本
<script src="https://cdnjs.cloudflare.com/ajax/libs/bodymovin/5.7.3/lottie.min.js" integrity="sha512-35O/v2b9y+gtxy3HK+G3Ah60g1hGfrxv67nL6CJ/T56easDKE2TAukzxW+/WOLqyGE7cBg0FR2KhiTJYs+FKrw==" crossorigin="anonymous"></script>

或者

$ npm install lottie-web
  • 加载动画
lottie.loadAnimation({
  container: document.getElementById('container'), // the dom element that will contain the animation
  renderer: 'svg',
  loop: false,
  autoplay: true,
  path: './data/data.json' // the path to the animation json
});

是的,就是这么简单,动画就渲染出来了。在正式看到json长什么样子之前,我们先用AE画一个简单的动画。这里我先画一个只有图片淡入淡出的简单动画(效果比较丑,哈哈哈哈),看图:

然后使用bodymovie导出,先完整看一下这个json。

{
    "v": "5.6.10", // 使用bodymovie插件的版本
    "fr": 32, // 帧速率
    "ip": 0, // 合成开始时间
    "op": 64, // 合成持续时间
    "w": 750, // 合成宽度
    "h": 1334, // 合成高度
    "nm": "合成 1", // 合成名
    "ddd": 0, // 是否3d图层
    "assets": [ // 使用的资源
        {
            "id": "image_0", // 使用的资源id
            "w": 750, // 当前图片资源的宽
            "h": 1334, // 当前图片资源的高
            "u": "images/", // 当前图片导出后在使用bodymovie导出后的文件夹
            "p": "img_0.jpg", // 当前图片资源路径
            "e": 0 // e=0 后将拼接u+p作为图片路径,e=1 不使用u,直接使用p的路径。
        }
   ],
   "layers": [ // 图层
	{
            "ddd": 0, // 是否是3d图层
            "ind": 1, // 当前图层所在的索引
            "ty": 2, // 2代表图片图层
            "nm": "img_0.jpg", // 源名称
            "cl": "jpg", // 后缀
            "refId": "image_0", // 使用assets中的id
            "sr": 1, // 图层 =>时间=>时间伸缩
            "ks": { // 图层 => 变换
		o": { // 透明度
                    "a": 1, // 是否是关键帧
                    "k": [ // 如果是关键帧时是数组
                        { // 每一个关键帧位置的配置信息
                            "i": { "x": [0.833], "y": [0.833] }, // 当前贝塞尔曲线的入值,这个是在lottie中写死的值
                            "o": { "x": [0.167], "y": [0.167] }, // 当前贝塞尔曲线的出值,这个是在lottie中写死的值
                            "t": 0, // 当前关键帧开始时间 
                            "s": [60] // 开始的opacity 
                        },
                        { // 第二个关键帧的配置信息
                            "i": { "x": [0.833], "y": [0.833] },
                            "o": { "x": [0.167], "y": [0.167] },
                            "t": 25,
                            "s": [100]
                        }, // 第三个关键帧的配置信息
                        {
                            "i": { "x": [0.833], "y": [0.833] },
                            "o": { "x": [0.167], "y": [0.167] },
                            "t": 30,
                            "s": [100]
                        }, // 第四个关键帧的配置信息
			{ "t": 50, "s": [50] }
                    ],
                    "ix": 11 // Property Index. Used for expressions。表达式标记。还没研究到这个怎么用
                },
            "r": { // 旋转
                "a": 0, // 是否是关键帧, 0代表不是关键帧
                "k": 0, // 不是关键帧时为number,旋转角度为0
                "ix": 10 // Property Index. Used for expressions。表达式标记
             }, 
             "p": { // 位置
                "a": 1, // 是关键帧
                "k": [
                       {
                            "i": { "x": 0.833, "y": 0.833 }, // 当前贝塞尔曲线的入值,这个是在lottie中写死的值
                            "o": { "x": 0.167, "y": 0.167 }, // 当前贝塞尔曲线的出值,这个是在lottie中写死的值
                            "t": 0, // 开始时间
                            "s": [-375, 675, 0], // 当前关键帧位置,横坐标-375,纵坐标675, 不是3d图层,z方向为0
                            "to": [125, 0, 0], // In Spatial Tangent. Only for spatial properties. Array of numbers. 入值 还不知道空间切线在AE中是个什么鬼
                            "ti": [-125, 0, 0] // Out Spatial Tangent. Only for spatial properties. Array of numbers. 出值 还不知道空间切线在AE中是个什么鬼
                      },
                      { 
                        "i": { "x": 0.833, "y": 0.833 },
                        "o": { "x": 0.167, "y": 0.167 },
                        "t": 25,
                        "s": [375, 675, 0],
                        "to": [0, 0, 0],
                        "ti": [0, 0, 0]
                     },
                     {
                        "i": { "x": 0.833, "y": 0.833 },
                        "o": { "x": 0.167, "y": 0.167 },
                        "t": 30,
                        "s": [375, 675, 0],
                        "to": [125.167, 0, 0]
                        "ti": [-125.167, 0, 0]
                    },
                    { "t": 50, "s": [1126, 675, 0] }
                ],
               "ix": 2 // Property Index. Used for expressions.
            },
            "a": { // 锚点
              "a": 0, // 不是关键帧
              "k": [375, 667, 0], // 锚点值
              "ix": 1 // Property Index. Used for expressions.
            }, 
            "s": { // 缩放比例
              "a": 0, // 不是关键帧
              "k": [100, 100, 100],  // // 缩放比例值
              "ix": 6 // Property Index. Used for expressions.
            } 
         },
        "ao": 0, // 是否自动跟踪
        "ip": 0, // 开始帧
        "op": 64, // 持续帧长
        "st": 0, // 开始时间
        "bm": 0 // 混合模式
         }
    ],
    "markers": []
}

解析JSON

这是一个比较简单的json,现在我们来一一解析他的参数:

视频的基本信息

我们先看上面json中的1-10行

"v": "5.6.10", // 使用bodymovie插件的版本
"fr": 32, // 帧速率
"ip": 0, // 合成开始时间
"op": 64, // 合成持续时间
"w": 750, // 合成宽度
"h": 1334, // 合成高度
"nm": "合成 1", // 合成名
"ddd": 0, // 是否3d图层

看一下AE中对应的这些参数

 

相信大家都能看懂,就不废话了。接下来看assets里面有什么。

assets

先来解释一下assets是什么。assets列出了所有layers中引用的资源,包括图片、预合成、文字等等。

现在我们的assets比较简单只有一张图片的配置信息,让我们来解读一下,这些属性是什么意思。

图片配置信息

{
	"id": "image_0", // 使用的资源id
	"w": 750, // 当前图片资源的宽
	"h": 1334, // 当前图片资源的高
	"u": "images/", // 当前图片导出后在使用bodymovie导出后的文件夹
	"p": "img_0.jpg", // 当前图片资源路径
	"e": 0 // e=0 后将拼接u+p作为图片路径,e=1 不使用u,直接使用p的路径。
}

这个时候我们如果想用一张在线的图片链接来替换掉模板中的相对图片地址,有两种方式,

  • 将e设置为1, 然后将p的值改为在线图片的链接
  • 将e为0或者1都可以,将u属性删掉,然后将p的值改为在线图片的链接。

这是为什么呢?让我们来看下Lottie源码中对图片资源的处理。

function getAssetsPath(assetData, assetsPath, original_path) {
  var path = '';
  if (assetData.e) { // 如果e为1直接将图片路径设置为p的值
    path = assetData.p;
  } else if(assetsPath) { // 如果配置了统一的assetsPath,那么将图片路径设置为如果配置了统一的assetsPath + p
    var imagePath = assetData.p;
    if (imagePath.indexOf('images/') !== -1) {
      imagePath = imagePath.split('/')[1];
    }
    path = assetsPath + imagePath;
  } else { // 如果e不为1,也没有设置统一的assetsPath,那么将图片路径设置为 u + p
    path = original_path;
    path += assetData.u ? assetData.u : '';
    path += assetData.p;
  }
  return path;
}

layers

json中的layers代表了AE中的每一个图层的信息。因为我们现在只有一个图层,所以layers数组只有一项。

图片图层配置信息

因为我们用的到图片图层,所以看下图片图层的配置信息。

由于ks的配置有点长,我们等下单独把它拿出来解释。

		{
			"ddd": 0, // 是否是3d图层
			"ind": 1, // 当前图层所在的索引
			"ty": 2, // 2代表图片图层
			"nm": "img_0.jpg", // 源名称
			"cl": "jpg", // 后缀
			"refId": "image_0", // 使用assets中的id
			"sr": 1, // 图层 =>时间=>时间伸缩
			"ks": {}, // 变换
			"ao": 0, // 是否自动跟踪
			"ip": 0, // 开始帧
			"op": 64, // 持续帧长
			"st": 0, // 开始时间
			"bm": 0 // 混合模式
		}
  • ddd、ind、nm、cl比较简单,看上面注释就好了。
  • ty为2就代表是图片图层。ty还有其他值:0: 预合成,1: 固体图层 4: 形状图层 5:文字图层
  • sr是控制图层的时间伸缩的拉伸因数,1代表100%, 默认为1

\

  • ao 自动追踪, 目前还没在用到这个效果,等有用到了再研究下

  • ip 开始帧 op持续时间

当前图层所在的帧范围,刚好我们这个图层从0我的位置开始,持续了64帧

下面这种情况,那ip就是20帧, 持续时间是44帧

  • bm 混合模式默认是0

全部bm枚举值有:

enum BlendModeEnum {
  normal, // 正常 默认值
  multiply, // 正片叠底
  screen, // 屏幕
  overlay, // 叠加
  darken, // 变暗
  lighten, // 变亮
  colorDodge, // 颜色变淡
  colorBurn, // 颜色加深
  hardLight, // 强光
  softLight, // 柔光
  difference,// 差值
  exclusion,// 排除
  hue, // 色相
  saturation, // 饱和度
  color, // 颜色
  luminosity // 亮度
}

对应AE中:

  • ks 变换
ks: {
  o: {}, // 不透明度
  p: {}, // 位置
	a: {}, // 锚点
  s: {}, // 缩放比例
}

\

设置了相应的变换参数后,AE会将值列在图层区域

我们现在一个一个看过去,先看不透明度,将我们上面JSON和AE中的参数做一下对比。

不透明度
ks: {
	"o": { // 透明度
        "a": 1, // 是否是关键帧
        "k": [ // 如果是关键帧时是数组
            { // 第一个关键帧位置的配置信息
          "i": { "x": [0.833], "y": [0.833] }, // 当前贝塞尔曲线的入值,这个是在lottie中写死的值
          "o": { "x": [0.167], "y": [0.167] }, // 当前贝塞尔曲线的出值,这个是在lottie中写死的值
          "t": 0, // 当前关键帧开始时间 
          "s": [60] // 开始的opacity 
        },
        { // 第二个关键帧的配置信息
          "i": { "x": [0.833], "y": [0.833] },
          "o": { "x": [0.167], "y": [0.167] },
          "t": 25,
          "s": [100]
        }, // 第三个关键帧的配置信息
        {
          "i": { "x": [0.833], "y": [0.833] },
          "o": { "x": [0.167], "y": [0.167] },
          "t": 30,
          "s": [100]
        }, // 第四个关键帧的配置信息
        { "t": 50, "s": [50] }
      ],
    "ix": 11 // Property Index. Used for expressions。表达式标记。还没研究到这个怎么用
  },
}

o有实际上有四个属性: a、k、x(配置表达式信息,因为我们没有用到,所以现在没有)、ix。现在我们来解释每一个属性的含义。

  • a: 0 | 1。0代表不是关键帧,1代表是关键帧。如果没有接触过设计的同学可能不知道什么是关键帧。那么AE中关键帧是什么呢。

先看百度上对关键帧的解释:

计算机动画术语, 帧——就是动画中最小单位的单幅影像画面,相当于电影胶片上的每一格镜头。在动画软件的时间轴上帧表现为一格或一个标记。关键帧——相当于二维动画中的原画。指角色或者物体运动或变化中的关键动作所处的那一帧。关键帧与关键帧之间的动画可以由软件来创建,叫做过渡帧或者中间帧。

讲的简单点就是你要某个帧给动画打上特定的参数,那么这个位置的帧就叫关键帧。所以我们现在画面中给不透明度设置了四个关键帧。

  • k: 设置值。我们已经知道我们给不透明度设置了4个关键帧,所以k是一个数组,有四项7,每一项都是每一个关键帧配置的值。我们先看第一个关键帧的值。

{ // 第一个关键帧位置的配置信息
  "i": { "x": [0.833], "y": [0.833] }, // 当前贝塞尔曲线的入值,这个是在lottie中写死的值
  "o": { "x": [0.167], "y": [0.167] }, // 当前贝塞尔曲线的出值,这个是在lottie中写死的值
  "t": 0, // 当前关键帧开始时间 
  "s": [60] // 开始的opacity 
}

我们在AE中设置当前关键帧的不透明度是60,所以在我们的JSON中S的就是60,之所以是数组,等下面讲到配置位置的信息的时候就知道了。当前关键帧的所在的帧位置是0,所以t是0。i和o分别是一个贝塞尔曲线的入值和出值。这个好像是lottie中写死的参数,暂时还不道是干嘛的,先放着。看下lottie中是这两个值是什么含义。

var easeInBez = BezierFactory.getBezierEasing(0.333,0,.833,.833, 'easeIn').get;
var easeOutBez = BezierFactory.getBezierEasing(0.167,0.167,.667,1, 'easeOut').get;
var easeInOutBez = BezierFactory.getBezierEasing(.33,0,.667,1, 'easeInOut').get;

所以先放着,我们先知道i和o分别是一组贝塞尔曲线的值。

接下来看不透明度的第二个关键帧的信息。

{ // 第二个关键帧的配置信息
  "i": { "x": [0.833], "y": [0.833] },
  "o": { "x": [0.167], "y": [0.167] },
  "t": 25,
  "s": [100]
},

i和o同样是两组贝塞尔曲线,我们先不管。这时候我们第二个关键帧的所在的帧位置是25帧,所以t是25,不透明度是100,所以s是[100]。以此类推,第三个,第四个我们就不废话了。

  • ix:/ Property Index. Used for expressions。表达式标记。还没研究到这个怎么用
位置

我们已经讲完了不透明设置,现在再来看看位置的的设置。

ks: {
  	"p": { // 位置
            "a": 1, // 是关键帧
            "k": [
                    {
                            "i": { "x": 0.833, "y": 0.833 }, // 当前贝塞尔曲线的入值,这个是在lottie中写死的值
                            "o": { "x": 0.167, "y": 0.167 }, // 当前贝塞尔曲线的出值,这个是在lottie中写死的值
                            "t": 0, // 开始时间
                            "s": [-375, 675, 0], // 当前关键帧位置,横坐标-375,纵坐标675, 不是3d图层,z方向为0
                            "to": [125, 0, 0], // In Spatial Tangent. Only for spatial properties. Array of numbers. 入值 还不知道空间切线在AE中是个什么鬼
                            "ti": [-125, 0, 0] // Out Spatial Tangent. Only for spatial properties. Array of numbers. 出值 还不知道空间切线在AE中是个什么鬼
                    },
                    {
                            "i": { "x": 0.833, "y": 0.833 },
                            "o": { "x": 0.167, "y": 0.167 },
                            "t": 25,
                            "s": [375, 675, 0],
                            "to": [0, 0, 0],
                            "ti": [0, 0, 0]
                    },
                    {
                            "i": { "x": 0.833, "y": 0.833 },
                            "o": { "x": 0.167, "y": 0.167 },
                            "t": 30,
                            "s": [375, 675, 0],
                            "to": [125.167, 0, 0],
                            "ti": [-125.167, 0, 0]
                    },
                    { "t": 50, "s": [1126, 675, 0] }
            ],
            "ix": 2 // Property Index. Used for expressions.
    },
}

讲完了一个不透明度,我相信大家已经对参数的设置已经有了一定的了解。跟不透明度一样,a设置是否是关键帧,k设置当前关键帧的值,ix是表达式标记。

接下来我们还是看第一个关键帧的参数信息。

{
  "i": { "x": 0.833, "y": 0.833 }, // 当前贝塞尔曲线的入值,这个是在lottie中写死的值
  "o": { "x": 0.167, "y": 0.167 }, // 当前贝塞尔曲线的出值,这个是在lottie中写死的值
  "t": 0, // 开始时间
  "s": [-375, 675, 0], // 当前关键帧位置,横坐标-375,纵坐标675, 不是3d图层,z方向为0
  "to": [125, 0, 0], // In Spatial Tangent. Only for spatial properties. Array of numbers. 入值 还不知道空间切线在AE中是个什么鬼
  "ti": [-125, 0, 0] // Out Spatial Tangent. Only for spatial properties. Array of numbers. 出值 还不知道空间切线在AE中是个什么鬼
},

同样i和o是两组贝塞尔曲线,我们还是不管他。

第一个位置关键帧的所在帧时间是0,所以t是0。当前位置是x轴是-375.y轴是667的位置,不是3d图层所以z是0。所以s的值就是[-375, 675, 0]。

ti和to分贝是两组空间切线的值,还不知道是怎么用的,所以先放着。

同样的道理第二、第三、第四关键帧的信息也是这么来的,就不废话了吧。

锚点

现在我们来看锚点。同样先看AE中对锚点的配置。

在正式说锚点之前,先说一下什么是锚点。

锚点通俗一点就是控制当前图层的中心位置在哪里。看图:

当我们拖动图片,图片对锚点位置进行变化的。

\

发现,我们并没有对锚点打上关键帧,所以锚点就没有关键帧。

ks: {
 "a": { // 锚点
    "a": 0, // 不是关键帧
    "k": [375, 667, 0], // 锚点值
    "ix": 1 // Property Index. Used for expressions.
  }, 
}

可以看出现在a是0,代表了不是关键帧。k依然是当前锚点的信息。

缩放比例

ks: {
	"s": { // 缩放比例
  		"a": 0, // 不是关键帧
  		"k": [100, 100, 100],  // 缩放比例值
  		"ix": 6 // Property Index. Used for expressions.
		} 	
}

相信不用我多说,大家应该都能知道,当前缩放比例不是一个关键帧,x轴,y轴,z轴都是100%,没有进行缩放。

\

旋转

ks: {
 "r": { // 旋转
   "a": 0, // 是否是关键帧, 0代表不是关键帧
   "k": 0, // 不是关键帧时为number,旋转角度为0
   "ix": 10 // Property Index. Used for expressions。表达式标记
  }
}

没有给旋转打上关键帧,旋转角度为0

总结

至此,我们已经对图像图层的相关配置信息已经全部讲完了,希望对大家有所帮助。