Skip to content

Instantly share code, notes, and snippets.

@jeremyckahn
Forked from willbailey/example.html
Created April 11, 2012 03:46
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jeremyckahn/2356708 to your computer and use it in GitHub Desktop.
Save jeremyckahn/2356708 to your computer and use it in GitHub Desktop.
transformation library
/** 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;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment