转自:https://www.jianshu.com/p/5f57fb4ae9da?tdsourcetag=s_pcqq_aiomsg
Lottie和Spine Runtime
Lottie是Airbnb推出的可以解析AE导出的包含动画信息的json文件的库,支持Android、iOS,React Native等平台。Spine Runtime是Spine提供的Spine导出的动画解析的库,支持各种游戏引擎,如egret、cocos2d-x等。
Lottie渲染时需要提供一系列的图片,渲染不同帧的时候会使用组合不同的图片。Spine Runtime使用一个小图片合成的大图片,渲染时会取不同的部分来渲染。


实际开发中,Lottie在应用中用的多,Spine Runtime在游戏中用的多。但并不代表他们不能在另外的场景中使用。
在web应用中使用Spine Runtime
需求中涉及到动画,设计师没有使用AE,而是使用Spine来设计的。导出的文件也是Spine特有的格式,于是我就对Spine进行了调研。经过调研我发现Spine的Runtime中有Html Canvas,这就是他可用在web应用中的基础。

动画资源
Spine导出的文件有3个,xxx.atlas、xxx.json、xxx.png

xxx.png是图片文件,因为图片整合到了一起,所有有一个xxx.atlas文件来描述哪个小图片在什么地方。

动画实现代码
经过分析,整体流程就是加载资源后,通过不断的重绘来显示一帧帧的图片,图片的更新是通过时间的毫秒数来驱动的。不断重绘的逻辑:



上面是核心的不断重绘的机制和更新渲染内容的机制,整体的流程如下:

整体代码如下:
<!-- saved from url=(0068)http://esotericsoftware.com/files/runtimes/spine-ts/examples/canvas/ -->
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=windows-1252">
<script src="./js/spine-canvas.js"></script>
<style>
* { margin: 0; padding: 0; }
body, html { height: 100% }
canvas { position: absolute; width: 100% ;height: 100%; }
</style>
</head>
<body>
<canvas id="canvas" width="398" height="588"></canvas>
<script>
var lastFrameTime = Date.now() / 1000;
var canvas, context;
var assetManager;
var skeleton, state, bounds;
var skeletonRenderer;
// var skelName = “spineboy-ess”;
var skelName = “pk_list_flash”;
// var animName = “walk”;
var animName = “animation”;
function init () {
canvas = document.getElementById(“canvas”);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
context = canvas.getContext(“2d”);
skeletonRenderer <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">spine<span class="token punctuation">.</span>canvas<span class="token punctuation">.</span>SkeletonRenderer</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// enable debug rendering</span>
skeletonRenderer<span class="token punctuation">.</span>debugRendering <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
<span class="token comment">// enable the triangle renderer, supports meshes, but may produce artifacts in some browsers</span>
skeletonRenderer<span class="token punctuation">.</span>triangleRendering <span class="token operator">=</span> <span class="token boolean">false</span><span class="token punctuation">;</span>
assetManager <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">spine<span class="token punctuation">.</span>canvas<span class="token punctuation">.</span>AssetManager</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
assetManager<span class="token punctuation">.</span><span class="token function">loadText</span><span class="token punctuation">(</span><span class="token string">"assets/"</span> <span class="token operator">+</span> skelName <span class="token operator">+</span> <span class="token string">".json"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
assetManager<span class="token punctuation">.</span><span class="token function">loadText</span><span class="token punctuation">(</span><span class="token string">"assets/"</span> <span class="token operator">+</span> skelName<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"-pro"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"-ess"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">".atlas"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
assetManager<span class="token punctuation">.</span><span class="token function">loadTexture</span><span class="token punctuation">(</span><span class="token string">"assets/"</span> <span class="token operator">+</span> skelName<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"-pro"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"-ess"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">".png"</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">requestAnimationFrame</span><span class="token punctuation">(</span>run<span class="token punctuation">)</span><span class="token punctuation">;</span>
}
function run () {
if (assetManager.isLoadingComplete()) {
var data = loadSkeleton(skelName, animName, “default”);
skeleton = data.skeleton;
state = data.state;
bounds = data.bounds;
requestAnimationFrame(render);
} else {
requestAnimationFrame(run);
}
}
function loadSkeleton (name, initialAnimation, skin) {
if (skin === undefined) skin = “default”;
<span class="token comment">// Load the texture atlas using name.atlas and name.png from the AssetManager.</span>
<span class="token comment">// The function passed to TextureAtlas is used to resolve relative paths.</span>
atlas <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">spine<span class="token punctuation">.</span>TextureAtlas</span><span class="token punctuation">(</span>assetManager<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"assets/"</span> <span class="token operator">+</span> name<span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"-pro"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">replace</span><span class="token punctuation">(</span><span class="token string">"-ess"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span> <span class="token operator">+</span> <span class="token string">".atlas"</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">path</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token keyword">return</span> assetManager<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"assets/"</span> <span class="token operator">+</span> path<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Create a AtlasAttachmentLoader, which is specific to the WebGL backend.</span>
atlasLoader <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">spine<span class="token punctuation">.</span>AtlasAttachmentLoader</span><span class="token punctuation">(</span>atlas<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Create a SkeletonJson instance for parsing the .json file.</span>
<span class="token keyword">var</span> skeletonJson <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">spine<span class="token punctuation">.</span>SkeletonJson</span><span class="token punctuation">(</span>atlasLoader<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Set the scale to apply during parsing, parse the file, and create a new skeleton.</span>
<span class="token keyword">var</span> skeletonData <span class="token operator">=</span> skeletonJson<span class="token punctuation">.</span><span class="token function">readSkeletonData</span><span class="token punctuation">(</span>assetManager<span class="token punctuation">.</span><span class="token function">get</span><span class="token punctuation">(</span><span class="token string">"assets/"</span> <span class="token operator">+</span> name <span class="token operator">+</span> <span class="token string">".json"</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> skeleton <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">spine<span class="token punctuation">.</span>Skeleton</span><span class="token punctuation">(</span>skeletonData<span class="token punctuation">)</span><span class="token punctuation">;</span>
skeleton<span class="token punctuation">.</span>flipY <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> bounds <span class="token operator">=</span> <span class="token function">calculateBounds</span><span class="token punctuation">(</span>skeleton<span class="token punctuation">)</span><span class="token punctuation">;</span>
skeleton<span class="token punctuation">.</span><span class="token function">setSkinByName</span><span class="token punctuation">(</span>skin<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token comment">// Create an AnimationState, and set the initial animation in looping mode.</span>
<span class="token keyword">var</span> animationState <span class="token operator">=</span> <span class="token keyword">new</span> <span class="token class-name">spine<span class="token punctuation">.</span>AnimationState</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">spine<span class="token punctuation">.</span>AnimationStateData</span><span class="token punctuation">(</span>skeleton<span class="token punctuation">.</span>data<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
animationState<span class="token punctuation">.</span><span class="token function">setAnimation</span><span class="token punctuation">(</span><span class="token number">0</span><span class="token punctuation">,</span> initialAnimation<span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
animationState<span class="token punctuation">.</span><span class="token function">addListener</span><span class="token punctuation">(</span><span class="token punctuation">{</span>
<span class="token function-variable function">event</span><span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">trackIndex<span class="token punctuation">,</span> event</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// console.log("Event on track " + trackIndex + ": " + JSON.stringify(event));</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token function-variable function">complete</span><span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">trackIndex<span class="token punctuation">,</span> loopCount</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// console.log("Animation on track " + trackIndex + " completed, loop count: " + loopCount);</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token function-variable function">start</span><span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">trackIndex</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// console.log("Animation on track " + trackIndex + " started");</span>
<span class="token punctuation">}</span><span class="token punctuation">,</span>
<span class="token function-variable function">end</span><span class="token punctuation">:</span> <span class="token keyword">function</span><span class="token punctuation">(</span><span class="token parameter">trackIndex</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
<span class="token comment">// console.log("Animation on track " + trackIndex + " ended");</span>
<span class="token punctuation">}</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span>
<span class="token comment">// Pack everything up and return to caller.</span>
<span class="token keyword">return</span> <span class="token punctuation">{</span> skeleton<span class="token punctuation">:</span> skeleton<span class="token punctuation">,</span> state<span class="token punctuation">:</span> animationState<span class="token punctuation">,</span> bounds<span class="token punctuation">:</span> bounds <span class="token punctuation">}</span><span class="token punctuation">;</span>
}
function calculateBounds(skeleton) {
var data = skeleton.data;
skeleton.setToSetupPose();
skeleton.updateWorldTransform();
var offset = new spine.Vector2();
var size = new spine.Vector2();
skeleton.getBounds(offset, size, []);
return { offset: offset, size: size };
}
function render () {
var now = Date.now() / 1000;
var delta = now - lastFrameTime;
lastFrameTime = now;
<span class="token function">resize</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
state<span class="token punctuation">.</span><span class="token function">update</span><span class="token punctuation">(</span>delta<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">state</span><span class="token punctuation">.</span><span class="token function">apply</span><span class="token punctuation">(</span>skeleton<span class="token punctuation">)</span><span class="token punctuation">;</span>
skeleton<span class="token punctuation">.</span><span class="token function">updateWorldTransform</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
skeletonRenderer<span class="token punctuation">.</span><span class="token function">draw</span><span class="token punctuation">(</span>skeleton<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">requestAnimationFrame</span><span class="token punctuation">(</span>render<span class="token punctuation">)</span><span class="token punctuation">;</span>
}
function resize () {
var w = canvas.clientWidth;
var h = canvas.clientHeight;
if (canvas.width != w || canvas.height != h) {
canvas.width = w;
canvas.height = h;
}
<span class="token comment">// magic</span>
<span class="token keyword">var</span> centerX <span class="token operator">=</span> bounds<span class="token punctuation">.</span>offset<span class="token punctuation">.</span>x <span class="token operator">+</span> bounds<span class="token punctuation">.</span>size<span class="token punctuation">.</span>x <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> centerY <span class="token operator">=</span> bounds<span class="token punctuation">.</span>offset<span class="token punctuation">.</span>y <span class="token operator">+</span> bounds<span class="token punctuation">.</span>size<span class="token punctuation">.</span>y <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> scaleX <span class="token operator">=</span> bounds<span class="token punctuation">.</span>size<span class="token punctuation">.</span>x <span class="token operator">/</span> canvas<span class="token punctuation">.</span>width<span class="token punctuation">;</span>
<span class="token keyword">var</span> scaleY <span class="token operator">=</span> bounds<span class="token punctuation">.</span>size<span class="token punctuation">.</span>y <span class="token operator">/</span> canvas<span class="token punctuation">.</span>height<span class="token punctuation">;</span>
<span class="token keyword">var</span> scale <span class="token operator">=</span> Math<span class="token punctuation">.</span><span class="token function">max</span><span class="token punctuation">(</span>scaleX<span class="token punctuation">,</span> scaleY<span class="token punctuation">)</span> <span class="token operator">*</span> <span class="token number">1.2</span><span class="token punctuation">;</span>
<span class="token keyword">if</span> <span class="token punctuation">(</span>scale <span class="token operator"><</span> <span class="token number">1</span><span class="token punctuation">)</span> scale <span class="token operator">=</span> <span class="token number">1</span><span class="token punctuation">;</span>
<span class="token keyword">var</span> width <span class="token operator">=</span> canvas<span class="token punctuation">.</span>width <span class="token operator">*</span> scale<span class="token punctuation">;</span>
<span class="token keyword">var</span> height <span class="token operator">=</span> canvas<span class="token punctuation">.</span>height <span class="token operator">*</span> scale<span class="token punctuation">;</span>
context<span class="token punctuation">.</span><span class="token function">setTransform</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">1</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">,</span> <span class="token number">0</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
context<span class="token punctuation">.</span><span class="token function">scale</span><span class="token punctuation">(</span><span class="token number">1</span> <span class="token operator">/</span> scale<span class="token punctuation">,</span> <span class="token number">1</span> <span class="token operator">/</span> scale<span class="token punctuation">)</span><span class="token punctuation">;</span>
context<span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span><span class="token operator">-</span>centerX<span class="token punctuation">,</span> <span class="token operator">-</span>centerY<span class="token punctuation">)</span><span class="token punctuation">;</span>
context<span class="token punctuation">.</span><span class="token function">translate</span><span class="token punctuation">(</span>width <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">,</span> height <span class="token operator">/</span> <span class="token number">2</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
}
(function() {
init();
}());
</script>
</body></html>

总结
web中的动画有属性动画和帧动画两种,帧动画常用的库有Lottie和Spine Runtime,用哪一种取决于动效师使用的是AE还是Spine,其中Spine多用于游戏的动画。从图片资源的管理方式、支持的渲染方式和平台这几个方面比较了Lottie和Spine Runtime的区别:Spine 多用于游戏,图片资源整合到一起并且提供atlas文件来标明对应图片位置,支持canvas的渲染方式,支持各种游戏引擎。Lottie多用于应用,图片资源分开存放,支持canvas、svg、dom三种渲染方式,并且支持Android、ios、React Native等平台。仅从canvas角度看,两者区别并不大。
评论 (0)