Created
April 10, 2012 21:59
-
-
Save willbailey/2354906 to your computer and use it in GitHub Desktop.
transformation library
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE HTML> | |
<html> | |
<head> | |
<meta http-equiv="content-type" content="text/html; charset=utf-8"> | |
<title>Index</title> | |
<script type="text/javascript" charset="utf-8" src="https://raw.github.com/gist/2354906/aefea983f6ca0b0e22656726a74250608397ab83/transformations.js"></script> | |
<script type="text/javascript" charset="utf-8"> | |
document.addEventListener('DOMContentLoaded', function() { | |
// creating a transformable element | |
var transformable = Object.create(Transformations); | |
var el = document.createElement('div'); | |
el.style.cssText = 'width:200px; height: 200px; background: red;'; | |
document.body.appendChild(el); | |
transformable.initTransformations(el); | |
// example of chaining transformations | |
transformable | |
.transform({rotate:45, duration:0.5}) | |
.transform({translate:[200,200,0], duration: 1}) | |
.then(function(){alert('done')}); | |
}); | |
</script> | |
</head> | |
<body> | |
</body> | |
</html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** Transformations Queue operations **/ | |
var OPERATIONS = ['scale', 'rotate', 'translate']; | |
if (typeof exports === 'undefined') { | |
var exports = {}; | |
} | |
/** | |
* 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; |
This is just a work in progress library for doing matrix transforms on an element. I'm mixing it into some existing view classes I'm working on, but I provided a little example of using it standalone if you'd like to check it out.
Nice! This will helpful for some animation stuff I'm working on. I'll be sure to credit you as necessary. :)
Cool I'll let you know when I enhance it then. I want to make it cross browser compatible and cleanup a few things.
Great! No need to rush. This will be really useful for some Rekapi optimizations I have in mind, but I won't be able to to get to them for a little while.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Interesting. Is this for anything in particular?