Sprite canvas component

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/