<div id="canvas"></div>
/* Fullscren */
html, body {
  position: relative;
  width: 100%;
  height: 100%;
  margin: 0;
  overflow: hidden;
  background-color: #E6E6E6;
  font-family: sans-serif;
}

/* Centered canvas */
#canvas {
  position: absolute;
  top: 50%;
  left: 50%;
  -webkit-transform: translateX(-50%) translateY(-50%);
  -ms-transform: translateX(-50%) translateY(-50%);
  transform: translateX(-50%) translateY(-50%);
  overflow: hidden;
}
/*
  svgAnimation.js v1.0.0
  Licensed under the MIT license.
  https://www.opensource.org/licenses/mit-license.php

  Copyright 2016
  http://www.hellomichael.com/
*/

; (function(window) {
  'use strict';

  var svgAnimation = function (options) {
    var self = this;
    self.options = extend({}, self.options);      
    extend(self.options, options);
    self.init();
  };

  svgAnimation.prototype = {
    constructor: svgAnimation,

    options: {
      data:                 null,
      canvas:               null,
      svg:                  null,
      duration:             null,
      steps:                null
    },

    init: function() {
      var self = this;

      self.loadJSON(self.options.data, function (data) {
        self.loadSVG(self.options.canvas, self.options.svg, data.animations, (self.options.duration/self.options.steps));
      });
    },

    /*
      Loads the SVG into the DOM
      @param {Object}   canvas
      @param {String}   svg
      http://stackoverflow.com/questions/9723422/is-there-some-innerhtml-replacement-in-svg-xml
    */
    loadJSON: function(data, callback) {
      var self = this;

      // XML request
      var xobj = new XMLHttpRequest();
      xobj.open('GET', data, true);

      xobj.onreadystatechange = function() {
        // Success
        if (xobj.readyState === 4 && xobj.status === 200) {
          var json = JSON.parse(xobj.responseText);

          if (callback && typeof(callback) === "function") {
            callback(json);
          }
        }
      };

      xobj.send(null);
    },

    /*
      Loads the SVG into the DOM and creates tweens ready for playback

      @param {Object}   canvas
      @param {String}   svg
      @param {Array}    animations
      @param {Int}      duration 
      @param {Function} callback 
    */

    loadSVG: function(canvas, svg, animations, duration) {
      var self = this;

      Snap.load(svg, function(data) {
        // Place SVG nodes into DOM tree
        canvas.append(data);

        // Create tweens for each animation
        animations.forEach(function(animation) {
          var element = canvas.select(animation.id);
          
          // Create scale, rotate, and transform groups around an SVG node
          self.createTransformGroup(element);

          // Create tween based on keyframes
          if (animation.keyframes.translateKeyframes) {
            new svgTween({
              element: element.select('.translate'),
              keyframes: animation.keyframes.translateKeyframes,
              duration: duration
            });
          }

          if (animation.keyframes.rotateKeyframes) {
           new svgTween({
              element: element.select('.rotate'),
              keyframes: animation.keyframes.rotateKeyframes,
              duration: duration
            });
          }

          if (animation.keyframes.scaleKeyframes) {
            new svgTween({
              element: element.select('.scale'),
              keyframes: animation.keyframes.scaleKeyframes,
              duration: duration
            });
          }
        });
      });
    },

    /*
      Create scale, rotate, and transform groups around an SVG DOM node
      @param {object} Snap element
    */
    createTransformGroup: function(element) {
      if (element.node) {
        if (element.node) {
          var childNodes = element.selectAll('*');
          
          element.g().attr('class', 'translate')
            .g().attr('class', 'rotate')
            .g().attr('class', 'scale')
            .append(childNodes);
        }
      }
    }
  };

  var svgTween = function (options) {
    var self = this;
    self.options = extend({}, self.options);      
    extend(self.options, options);
    self.init();
  };

  svgTween.prototype = {
    constructor: svgTween,

    options: {
      element:    null,
      type:       null,
      keyframes:  null,
      duration:   null,
      originX:    null,
      originY:    null
    },

    init: function () {
      var self = this;

      // Set type
      self.options.type = self.options.element.node.getAttributeNode('class').value;

      // Set bbox to specific transform element (.translate, .scale, .rotate)
      var bBox = self.options.element.getBBox();

      // Set origin as specified or default to center
      self.options.originX = self.options.keyframes[0].cx ? self.getOriginX(bBox, self.options.keyframes[0].cx) : self.getOriginX(bBox, 'center');
      self.options.originY = self.options.keyframes[0].cy ? self.getOriginY(bBox, self.options.keyframes[0].cy) : self.getOriginY(bBox, 'center');

      // Reset and play tween
      self.resetTween(self.options.element, self.options.type, self.options.keyframes, self.options.originX, self.options.originY);
      self.playTween(self.options.element, self.options.type, self.options.keyframes, self.options.originX, self.options.originY, self.options.duration, 0);
    },

    /*
      Recursively loop through keyframes to create pauses or tweens

      @param {Object} element
      @param {String} type - "scale", "rotate", "translate"
      @param {Array}  keyframes
      @param {String} originX - "left", "right", "center"
      @param {String} originY - "top", "bottom", "center"
      @param {Int}    duration 
      @param {Int}    index
    */
    playTween: function(element, type, keyframes, originX, originY, duration, index) {
      var self = this, transform, translateX, translateY, rotationAngle, scaleX, scaleY, newDuration, easing;
      
      // Set keyframes we're transitioning to
      if (type === 'translate') {
        translateX = keyframes[index].x;
        translateY = keyframes[index].y;
        transform = 'T ' + translateX + ' ' + translateY;
      }

      else if (type === 'rotate') {
        rotationAngle = keyframes[index].angle;
        transform = 'R ' + rotationAngle + ' ' + originX + ' ' + originY;
      }

      else if (type === 'scale') {
        scaleX = keyframes[index].x;
        scaleY = keyframes[index].y;
        transform = 'S ' + scaleX + ' ' + scaleY + ' ' + originX + ' ' + originY;
      }
      
      // Set duration as an initial pause or the difference of steps in between keyframes
      newDuration = index ? ((keyframes[index].step - keyframes[(index-1)].step) * duration) : (keyframes[index].step * duration);

      // Set easing parameter
      easing = mina[keyframes[index].easing];

      // Skip first tween if animation immediately starts on step 0
      if (index === 0 && keyframes[index].step === 0) {
        self.playTween(element, type, keyframes, originX, originY, duration, (index + 1));
      }

      // Or pause tween if initial keyframe
      else if (index === 0 && keyframes[index].step !== 0) {
        setTimeout(function() {
          if (index !== (keyframes.length - 1)) {
            self.playTween(element, type, keyframes, originX, originY, duration, (index + 1));
          }
        }, newDuration);
      }

      // Or animate tweens if keyframes exist
      else {
        element.animate({
          transform: transform
        }, newDuration, easing, function() {

          
          if (index !== (keyframes.length - 1)) {
            self.playTween(element, type, keyframes, originX, originY, duration, (index + 1));
          }
        });
      }
    },

    /*
      Resets the illustration to the first keyframe

      @param {Object} element
      @param {String} type - "scale", "rotate", "translate"
      @param {Array}  keyframes
      @param {String} originX - "left", "right", "center"
      @param {String} originY - "top", "bottom", "center"
    */
    resetTween: function (element, type, keyframes, originX, originY) {
      var transform, translateX, translateY, rotationAngle, scaleX, scaleY;
      
      if (type === 'translate') {
        translateX = keyframes[0].x;
        translateY = keyframes[0].y;
        transform = 'T ' + translateX + ' ' + translateY;
      }

      else if (type === 'rotate') {
        rotationAngle = keyframes[0].angle;
        transform = 'R ' + rotationAngle + ' ' + originX + ' ' + originY;
      }

      else if (type === 'scale') {
        scaleX = keyframes[0].x;
        scaleY = keyframes[0].y;
        transform = 'S ' + scaleX + ' ' + scaleY + ' ' + originX + ' ' + originY;
      }

      element.transform(transform);
    },

    /*
      Translates the horizontal origin from a string to pixel value

      @param {Object}     Snap bBox
      @param {String}     "left", "right", "center"
      @return {Object}    pixel value
    */
    getOriginX: function (bBox, direction) {
      if (direction === 'left') {
        return bBox.x;
      }

      else if (direction === 'center') {
        return bBox.cx;
      }

      else if (direction === 'right') {
        return bBox.x2;
      }
    },

    /*
      Translates the vertical origin from a string to pixel value

      @param {Object}     Snap bBox
      @param {String}     "top", "bottom", "center"
      @return {Object}    pixel value
    */
    getOriginY: function (bBox, direction) {
      if (direction === 'top') {
        return bBox.y;
      }

      else if (direction === 'center') {
        return bBox.cy;
      }

      else if (direction === 'bottom') {
        return bBox.y2;
      }
    }
  };

  /*
    Merges two objects together
    @param  {Object}  a 
    @param  {Object}  b
    @return {Object}  sum
    http://stackoverflow.com/questions/11197247/javascript-equivalent-of-jquerys-extend-method
  */
  function extend(a, b) {
    for (var key in b) { 
      if (b.hasOwnProperty(key)) {
        a[key] = b[key];
      }
    }

    return a;
  }

  // Add to global namespace
  window.svgAnimation = svgAnimation;
  window.svgTween = svgTween;
})(window);

/*
  svgAnimation.js v1.0.0
  Licensed under the MIT license.
  https://www.opensource.org/licenses/mit-license.php

  Copyright 2016
  http://www.hellomichael.com/
*/

; (function(window) {
  'use strict';

  var svgAnimation = function (options) {
    var self = this;
    self.options = extend({}, self.options);      
    extend(self.options, options);
    self.init();
  };

  svgAnimation.prototype = {
    constructor: svgAnimation,

    options: {
      data:                 null,
      canvas:               null,
      svg:                  null,
      duration:             null,
      steps:                null
    },

    init: function() {
      var self = this;

      self.loadJSON(self.options.data, function (data) {
        self.loadSVG(self.options.canvas, self.options.svg, data.animations, (self.options.duration/self.options.steps));
      });
    },

    /*
      Loads the SVG into the DOM
      @param {Object}   canvas
      @param {String}   svg
      http://stackoverflow.com/questions/9723422/is-there-some-innerhtml-replacement-in-svg-xml
    */
    loadJSON: function(data, callback) {
      var self = this;

      // XML request
      var xobj = new XMLHttpRequest();
      xobj.open('GET', data, true);

      xobj.onreadystatechange = function() {
        // Success
        if (xobj.readyState === 4 && xobj.status === 200) {
          var json = JSON.parse(xobj.responseText);

          if (callback && typeof(callback) === "function") {
            callback(json);
          }
        }
      };

      xobj.send(null);
    },

    /*
      Loads the SVG into the DOM and creates tweens ready for playback

      @param {Object}   canvas
      @param {String}   svg
      @param {Array}    animations
      @param {Int}      duration 
      @param {Function} callback 
    */

    loadSVG: function(canvas, svg, animations, duration) {
      var self = this;

      Snap.load(svg, function(data) {
        // Place SVG nodes into DOM tree
        canvas.append(data);

        // Create tweens for each animation
        animations.forEach(function(animation) {
          var element = canvas.select(animation.id);
          
          // Create scale, rotate, and transform groups around an SVG node
          self.createTransformGroup(element);

          // Create tween based on keyframes
          if (animation.keyframes.translateKeyframes) {
            new svgTween({
              element: element.select('.translate'),
              keyframes: animation.keyframes.translateKeyframes,
              duration: duration
            });
          }

          if (animation.keyframes.rotateKeyframes) {
           new svgTween({
              element: element.select('.rotate'),
              keyframes: animation.keyframes.rotateKeyframes,
              duration: duration
            });
          }

          if (animation.keyframes.scaleKeyframes) {
            new svgTween({
              element: element.select('.scale'),
              keyframes: animation.keyframes.scaleKeyframes,
              duration: duration
            });
          }
        });
      });
    },

    /*
      Create scale, rotate, and transform groups around an SVG DOM node
      @param {object} Snap element
    */
    createTransformGroup: function(element) {
      if (element.node) {
        if (element.node) {
          var childNodes = element.selectAll('*');
          
          element.g().attr('class', 'translate')
            .g().attr('class', 'rotate')
            .g().attr('class', 'scale')
            .append(childNodes);
        }
      }
    }
  };

  var svgTween = function (options) {
    var self = this;
    self.options = extend({}, self.options);      
    extend(self.options, options);
    self.init();
  };

  svgTween.prototype = {
    constructor: svgTween,

    options: {
      element:    null,
      type:       null,
      keyframes:  null,
      duration:   null,
      originX:    null,
      originY:    null
    },

    init: function () {
      var self = this;

      // Set type
      self.options.type = self.options.element.node.getAttributeNode('class').value;

      // Set bbox to specific transform element (.translate, .scale, .rotate)
      var bBox = self.options.element.getBBox();

      // Set origin as specified or default to center
      self.options.originX = self.options.keyframes[0].cx ? self.getOriginX(bBox, self.options.keyframes[0].cx) : self.getOriginX(bBox, 'center');
      self.options.originY = self.options.keyframes[0].cy ? self.getOriginY(bBox, self.options.keyframes[0].cy) : self.getOriginY(bBox, 'center');

      // Reset and play tween
      self.resetTween(self.options.element, self.options.type, self.options.keyframes, self.options.originX, self.options.originY);
      self.playTween(self.options.element, self.options.type, self.options.keyframes, self.options.originX, self.options.originY, self.options.duration, 0);
    },

    /*
      Recursively loop through keyframes to create pauses or tweens

      @param {Object} element
      @param {String} type - "scale", "rotate", "translate"
      @param {Array}  keyframes
      @param {String} originX - "left", "right", "center"
      @param {String} originY - "top", "bottom", "center"
      @param {Int}    duration 
      @param {Int}    index
    */
    playTween: function(element, type, keyframes, originX, originY, duration, index) {
      var self = this, transform, translateX, translateY, rotationAngle, scaleX, scaleY, newDuration, easing;
      
      // Set keyframes we're transitioning to
      if (type === 'translate') {
        translateX = keyframes[index].x;
        translateY = keyframes[index].y;
        transform = 'T ' + translateX + ' ' + translateY;
      }

      else if (type === 'rotate') {
        rotationAngle = keyframes[index].angle;
        transform = 'R ' + rotationAngle + ' ' + originX + ' ' + originY;
      }

      else if (type === 'scale') {
        scaleX = keyframes[index].x;
        scaleY = keyframes[index].y;
        transform = 'S ' + scaleX + ' ' + scaleY + ' ' + originX + ' ' + originY;
      }
      
      // Set duration as an initial pause or the difference of steps in between keyframes
      newDuration = index ? ((keyframes[index].step - keyframes[(index-1)].step) * duration) : (keyframes[index].step * duration);

      // Set easing parameter
      easing = mina[keyframes[index].easing];

      // Skip first tween if animation immediately starts on step 0
      if (index === 0 && keyframes[index].step === 0) {
        self.playTween(element, type, keyframes, originX, originY, duration, (index + 1));
      }

      // Or pause tween if initial keyframe
      else if (index === 0 && keyframes[index].step !== 0) {
        setTimeout(function() {
          if (index !== (keyframes.length - 1)) {
            self.playTween(element, type, keyframes, originX, originY, duration, (index + 1));
          }
        }, newDuration);
      }

      // Or animate tweens if keyframes exist
      else {
        element.animate({
          transform: transform
        }, newDuration, easing, function() {

          
          if (index !== (keyframes.length - 1)) {
            self.playTween(element, type, keyframes, originX, originY, duration, (index + 1));
          }
        });
      }
    },

    /*
      Resets the illustration to the first keyframe

      @param {Object} element
      @param {String} type - "scale", "rotate", "translate"
      @param {Array}  keyframes
      @param {String} originX - "left", "right", "center"
      @param {String} originY - "top", "bottom", "center"
    */
    resetTween: function (element, type, keyframes, originX, originY) {
      var transform, translateX, translateY, rotationAngle, scaleX, scaleY;
      
      if (type === 'translate') {
        translateX = keyframes[0].x;
        translateY = keyframes[0].y;
        transform = 'T ' + translateX + ' ' + translateY;
      }

      else if (type === 'rotate') {
        rotationAngle = keyframes[0].angle;
        transform = 'R ' + rotationAngle + ' ' + originX + ' ' + originY;
      }

      else if (type === 'scale') {
        scaleX = keyframes[0].x;
        scaleY = keyframes[0].y;
        transform = 'S ' + scaleX + ' ' + scaleY + ' ' + originX + ' ' + originY;
      }

      element.transform(transform);
    },

    /*
      Translates the horizontal origin from a string to pixel value

      @param {Object}     Snap bBox
      @param {String}     "left", "right", "center"
      @return {Object}    pixel value
    */
    getOriginX: function (bBox, direction) {
      if (direction === 'left') {
        return bBox.x;
      }

      else if (direction === 'center') {
        return bBox.cx;
      }

      else if (direction === 'right') {
        return bBox.x2;
      }
    },

    /*
      Translates the vertical origin from a string to pixel value

      @param {Object}     Snap bBox
      @param {String}     "top", "bottom", "center"
      @return {Object}    pixel value
    */
    getOriginY: function (bBox, direction) {
      if (direction === 'top') {
        return bBox.y;
      }

      else if (direction === 'center') {
        return bBox.cy;
      }

      else if (direction === 'bottom') {
        return bBox.y2;
      }
    }
  };

  /*
    Merges two objects together
    @param  {Object}  a 
    @param  {Object}  b
    @return {Object}  sum
    http://stackoverflow.com/questions/11197247/javascript-equivalent-of-jquerys-extend-method
  */
  function extend(a, b) {
    for (var key in b) { 
      if (b.hasOwnProperty(key)) {
        a[key] = b[key];
      }
    }

    return a;
  }

  // Add to global namespace
  window.svgAnimation = svgAnimation;
  window.svgTween = svgTween;
})(window);

(function() {
  setTimeout(function(){
      var backpack = new svgAnimation({
    canvas: new Snap('#canvas'),
    svg: 'https://raw.githubusercontent.com/hellomichael/svgAnimation/master/example/svg/backpack.svg',
    data: 'https://raw.githubusercontent.com/hellomichael/svgAnimation/master/example/json/backpack.json',
    duration: 2000,
    steps: 10
  });
  }, 500);
})();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://rawgit.com/hellomichael/svgAnimation/master/example/js/libs/snap.svg.min.js
  2. https://rawgit.com/overjase/snap-easing/master/snap.svg.easing.js