- /** Transformations Queue operations **/
- var OPERATIONS = ['scale', 'rotate', 'translate'];
- /**
- * A mixin to assist in managing 3d matrix transformations on a dom element
- */
- var Transformations = exports.Transformations = {
- /**
- * initialize the transformations mixin
- */
- initTransformations: function(el) {
- this._el = el;
- this._queue = [];
- this.pushMatrix();
- this._scaleMatrix = new WebKitCSSMatrix();
- this._rotationXMatrix = new WebKitCSSMatrix();
- this._rotationYMatrix = new WebKitCSSMatrix();
- this._rotationZMatrix = new WebKitCSSMatrix();
- },
- /**
- * set the opacity to be applied at the next commit
- */
- setOpacity: function(opacity) {
- this._opacity = opacity;
- return this;
- },
- /**
- * rotate the current matrix
- */
- rotate: function() {
- var o = this._normalizeArguments.apply(this, arguments);
- this._rotationXMatrix = this._rotationXMatrix.rotate(o.x,0,0);
- this._rotationYMatrix = this._rotationYMatrix.rotate(0,o.y,0);
- this._rotationZMatrix = this._rotationZMatrix.rotate(0,0,o.z);
- this.currentRotation = o;
- return this;
- },
- /**
- * scale the current matrix
- */
- scale: function() {
- var o = this._normalizeArguments.apply(this, arguments);
- this._scaleMatrix.m11 = o.x;
- this._scaleMatrix.m22 = o.y;
- this._scaleMatrix.m33 = o.z;
- this.currentScale = o;
- return this;
- },
- /**
- * perform a translation on the current matrix
- */
- translate: function() {
- var o = this._normalizeArguments.apply(this, arguments);
- this.setCurrentMatrix(this.getCurrentMatrix().translate(o.x, o.y, o.z));
- return this;
- },
- /**
- * retrieve the top matrix from the stack
- */
- getCurrentMatrix: function() {
- if (this._matrices.length === 0) {
- this.pushMatrix();
- }
- return this._matrices[this._matrices.length - 1];
- },
- /**
- * update the top matrix on the stack
- */
- setCurrentMatrix: function(val) {
- if (this._matrices.length === 0) {
- this.pushMatrix();
- }
- this._matrices[this._matrices.length - 1] = val;
- return this;
- },
- /**
- * push a matrix onto the stack. If no argument is provide an empty
- * matrix is automatically created
- */
- pushMatrix: function(matrix) {
- this._matrices = this._matrices || [];
- this._matrices.push(matrix || new WebKitCSSMatrix());
- return this;
- },
- /**
- * remove a matrix from the stack
- */
- popMatrix: function() {
- this._matrices.pop();
- return this;
- },
- /**
- * retrieve the current cumulative translation
- */
- cumulativeTranslation: function() {
- var transform = this.cumulativeTransformation();
- return {
- x: transform.m41,
- y: transform.m42,
- z: transform.m43
- };
- },
- /**
- * retrieve the current cumulative scale
- */
- cumulativeScale: function() {
- var transform = this.cumulativeTransformation();
- return {
- x: transform.m11,
- y: transform.m22,
- z: transform.m33
- };
- },
- /**
- * calculate the cumulative transformation matrix
- */
- cumulativeTransformation: function() {
- if (this._matrices.length === 0) {
- return new WebKitCSSMatrix();
- }
- var matrix = this._matrices.reduce(function(memo, value) {
- return memo.multiply(value);
- });
- matrix = matrix.multiply(this._rotationXMatrix);
- matrix = matrix.multiply(this._rotationYMatrix);
- matrix = matrix.multiply(this._rotationZMatrix);
- matrix = matrix.multiply(this._scaleMatrix);
- return matrix;
- },
- transform: function(options) {
- this._queue.unshift(options);
- if (options.duration) {
- if (!this._flushing) {
- this._flushing = true;
- setTimeout(this._flushQueue, 0, this);
- }
- } else {
- this._flushQueue(this);
- }
- return this;
- },
- then: function(callback) {
- this._queue.unshift(callback);
- if (!this._flushing) {
- this._flushing = true;
- setTimeout(this._flushQueue, 0, this);
- }
- return this;
- },
- save: function() {
- this._queue.unshift({save: true});
- if (!this._flushing) {
- this._flushing = true;
- setTimeout(this._flushQueue, 0, this);
- }
- return this;
- },
- revert: function(options) {
- options = options || {};
- options.revert = true;
- this._queue.unshift(options);
- if (options.duration) {
- if (!this._flushing) {
- this._flushing = true;
- setTimeout(this._flushQueue, 0, this);
- }
- } else {
- this._flushQueue(this);
- }
- return this;
- },
- _flushQueue: function(ctx) {
- var command = ctx._queue.pop();
- if (!command) {
- ctx._flushing = false;
- return;
- }
- var procede = (function() {
- return function() {
- ctx._flushQueue(ctx);
- };
- })();
- if (typeof command === 'function') {
- var wait = command(procede);
- if (!wait) {
- procede();
- }
- } else if (command.save) {
- ctx.pushMatrix();
- procede();
- } else if (command.revert) {
- ctx.popMatrix();
- ctx.commit(command, procede);
- } else {
- ctx._invokeCommand(command, procede)
- ctx.commit(command, procede);
- }
- },
- _invokeCommand: function(command) {
- for (var i = 0, len = OPERATIONS.length; i < len; i++) {
- var op = OPERATIONS[i];
- if (typeof command[op] !== 'undefined') {
- this[op].call(this, command[op]);
- }
- }
- },
- /**
- * apply the current stack of transformations
- */
- commit: function(options, callback) {
- options = options || {};
- if (options.duration || options.timing) {
- this._setupTransition(options, callback);
- }
- // commit the style changes
- if (typeof options.opacity !== 'undefined') {
- this._el.style.opacity = options.opacity;
- }
- this._el.style.webkitTransform = this.cumulativeTransformation().toString();
- return this;
- },
- _setupTransition: function(options, callback) {
- this._el.style.webkitTransitionProperty = '-webkit-transform, opacity';
- if (typeof options.duration !== 'undefined') {
- this._el.style.webkitTransitionDuration = this._formatDuration(options.duration);
- }
- if (typeof options.timing !== 'undefined') {
- this._el.style.webkitTransitionTimingFunction = options.timing;
- }
- if (callback) {
- var _this = this;
- this._el.addEventListener('webkitTransitionEnd', function(e) {
- _this._el.removeEventListener('webkitTransitionEnd', arguments.callee);
- _this._el.style.webkitTransitionProperty = '';
- _this._el.style.webkitTransitionDuration = '';
- _this._el.style.webkitTransitionTimingFunction = '';
- callback();
- e.stopPropagation();
- });
- }
- },
- /**
- * take the passed in arguments and convert them to a normalized object
- * with x, y, z properties
- * a single number argument will be converted to x:num, y:num, z:num
- * three number arguments will be converted to x:num1, y:num2, z:num2
- * an object argument will be left intact
- */
- _normalizeArguments: function() {
- var args = Array.prototype.slice.call(arguments, 0);
- if (args.length > 1) {
- return {x: args[0], y: args[1], z: args[2]};
- } else if (Array.isArray(args[0])) {
- var arg = args[0];
- return {x: arg[0], y: arg[1], z: arg[2]};
- } else if (Object(args[0]) === args[0]) {
- return args[0];
- } else {
- return {x: args[0], y: args[0], z: args[0]};
- }
- },
- _formatDuration: function(duration) {
- return duration + 's, ' + duration + 's';
- }
- };
- Transformations.xfrm = Transformations.transform;