动画原理
这里把动画方案的实现原理分为三种。
- 第一种是纯面向过程的动画记录方式,目前市面上主流的有Airbnb 的Lottie和腾讯最近开源的PAG,原理是通过记录动画设计师制作动画的每个元素,还有每个元素的运动过程,然后开发者再还原这些元素的运动。这个方案的优点是文件体积小,能实现大部分基础特效和变换下的动画。缺点是不支持比较复杂的特效(如3D效果等),播放性能也比较差,因为记录过程就必然有一个还原动画的计算过程,在渲染的同时还需要计算每个元素的变化过程,对比其他方案就多了一项计算的性能损耗。
- 第二种是半面向过程半结果的动画记录方式 目前市面上主流的方案有YY的SVGA和Y2A方案,原理是记录动画设计师制作动画时用到的每一个元素,然后记录元素在每一帧的特征(形状,大小,位置,alpha等)。SVGA的优点是能支持更复杂的动画,同时播放性能也比第一种方案好许多。缺点是体积会大一些,同时也不支持复杂的特效(如3D效果等)。为什么第二种方案相比第一种方案性能上比较好,也能够支持更复杂的动画呢,这是牺牲了一部分文件体积,从而减少了计算这一环节的因素。SVGA的文件经过zlib压缩和base64编码后,压缩率能达到85%~90%
- 第三种是记录结果方式,目前主要有PNG序列图和带透明通道的MP4方案。原理是记录每一帧每个像素点的像素值来渲染,优点是所见即所得,充分解放设计师的思想,能够支持所有设计师能设计出来的动画,包括3D动效等
总结:
- 第一种方案 支持一般的动效,比如UI动画,场景切换动效和一些进场、得分等动效。
- 第二种方案能支持第一种能支持的所有动效,且能够支持更复杂的动效,例如复杂的礼物动效,酷炫的坐骑动画等。
- 第三种方案能支持所有的动效,只要设计师能设计出来的动效都支持。在体积上,也是依次递增的,第一种方案体积最小。但是在性能上刚好反过来,第一种方案性能最差,CPU和GPU占用较高。
Lottie
Lottie是Airbnb开源的一个支持 Android、iOS 以及 ReactNative,利用json文件的方式快速实现动画效果的库。
设计师可以使用 Adobe After Effects 设计出漂亮的动画之后,使用 Lottic
提供的 Bodymovin 插件将设计好的动画导出成 JSON 格式,就可以直接运用在 iOS
、Android
、Web
和 React Native
之上,无需其他额外操作。
Lottie-web
Lottie-web是爱彼迎提供的针对html的lottie播放器版本
usage
npm i lottie-web --save
import lottie from 'lottie-web/build/player/lottie_light_canvas';
import JSONL from './ani/dataModal.json';
const modalRef = useRef();
modalRef.current = lottie.loadAnimation({
container: modalRef?.current,
renderer: 'canvas',
animationData: JSONL,
autoplay: true,
loop: false,
});
<div className="rock-lottie" ref={modalRef} />
lottie的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文件,来修改我们的lottie资源
import JSONL from './ani/dataModal.json';
JSONL.assets[9].p = 'xxxurl';
JSONL.assets[8].p = 'xxxurl';
JSONL.assets[5].p = 'xxxurl';
JSONL.assets[4].p = 'xxxurl';
JSONL.assets[7].p = 'xxxurl';
JSONL.assets[6].p = 'xxxurl';
modalRef.current = lottie.loadAnimation({
container: modalRef?.current,
renderer: 'canvas',
animationData: JSONL,
autoplay: true,
loop: false,
});