Recently some of my friends have been talking about using SVG animation for various things, and although I broadly speaking support the idea, it made me want to build something along those lines as well.
The basic idea was to just support all the data inline:
<div id="foo" class="component--sprite">
<meta data-cell-x="32" data-cell-y="32" data-src="simple.png" data-fps="30"/>
<meta data-start="0" data-end="32"/>
</div>
Initially it was just going to be a simple declarative component to render frames from a sprite sheet on a canvas using the amazing Pixi.js.
However, after briefly starting down that path, I realized that Pixi.js suffers from a fatal flaw; it assumes a single rendering context.
Although this is, in general, a good design decision, it has the downside that as a 'simple drop-in component', its not really suitable; basically, it doesn't play nicely with others.
So instead I started down the 'just do it yourself!' path, and surprisingly it turns out this is a lot more complicated than I initially expected.
In order to be useable, the component actually had to have a number of other features I didn't anticipate until I actually tried to use it:
In the end I went with a more complex markup for the component, which I'm still not 100% happy with, but it certainly made it a lot more useful:
<div id="sara" class="component--sprite" data-active="s1">
<meta data-width="64" data-height="70" data-src="LPC_Sara/SaraFullSheet.png" data-fx="13" data-fy="21" data-fps="10"/>
<meta data-state="s1" data-offset="0" data-length="7" data-loops="true"/>
<meta data-state="s2" data-offset="13" data-length="7" data-loops="true"/>
</div>
Another was that in order to achieve the appropriate FPS a polyfill for the requestAnimationFrame
javascript API, which is here, as a typescript drop in:
/** Node safety */
declare var window;
function has_window() {
return typeof window != 'undefined';
}
/** Request animation polyfill from http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ */
function requestAnimationFramePolyfill() {
var lastTime = 0;
var vendors = ['webkit', 'moz'];
for (var x = 0; x < vendors.length && !window['requestAnimationFrame']; ++x) {
window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
window.cancelAnimationFrame =
window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame) {
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}
}
/** Invoke an action async */
export function async(action:any):void {
if (has_window()) {
window['requestAnimationFrame'](action);
}
else {
setTimeout(action, 1);
}
}
// Ensure we have some kind of animation helper
if (has_window()) {
requestAnimationFramePolyfill();
}
Anyhow, although I wouldn't recommend giving it wide use, since a single canvas is overwhelmingly faster, this is a cute drop in for simple gif-life animations.
I'd love to come back to this one day and fork it to support rendering video too~
The full code is under MIT license and available on https://github.com/shadowmint/iwc-sprite/