Created
June 4, 2015 09:07
-
-
Save Martin-Pitt/3a6f2168a03a7502575a to your computer and use it in GitHub Desktop.
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
/// Transform.js | |
/* | |
http://www.w3.org/TR/css3-transforms/#interpolation-of-transforms | |
Four rules for interpolating transform lists: | |
* If both lists are none, return nothing. | |
* If one of the lists is none, create a equivalent identity list, continue to next rule. | |
* If both lists have the same amount of arguments (having a common primitive), interpolate each pair of transform function and return computed value. | |
* else in worst case, convert both lists to matrices and interpolate those, return computed value. | |
To minimize GC, we have only one static object. | |
Although string manip is nasty, so need to optimise in future. | |
Usage: | |
Transform.from('transforms(...)').to('transforms(...)').interpolate(0.5); | |
Note: | |
For now, keep things straightforward. We can expand later. | |
These are the foundations, although still a prototype. | |
*/ | |
window.Transform = { | |
from: function(from) { this._from = this.parse(from); return this; }, | |
to: function(to) { this._to = this.parse(to); return this; }, | |
interpolate: function(t) { | |
/// Check for empty lists | |
if(!this._from.length && !this._to.length) return 'none'; | |
/// If a list is empty, build identity list | |
if(!this._from.length || !this._to.length) | |
{ | |
if(!this._from.length) this._from = this.identity(this._to); | |
else if(!this._to.length) this._to = this.identity(this._from); | |
} | |
/// Otherwise, check for common root / whether to switch to matrices | |
else | |
{ | |
} | |
/// Compute | |
var computed = []; | |
for(var iter = 0, total = Math.min(this._from.length, this._to.length); iter < total; ++iter) | |
{ | |
var from = this._from[iter]; | |
var to = this._to[iter]; | |
var tween = this.mixFunctions(from, to, t); | |
computed.push(tween); | |
} | |
for(var larger = this._from.length > this._to.length, total = larger? this._from.length: this._to.length; iter < total; ++iter) | |
{ | |
computed.push((larger? this._from: this._to)[iter].slice(0)); | |
} | |
return this.stringify(computed); | |
}, | |
parse: function(str) { | |
/* | |
Turns the CSS transform like "translate(20px, 150px) rotate(30deg) translateY(-50%)" | |
into a transform list: [ | |
['translate', [20, 'px'], [150, 'px']], | |
['rotate', [30, 'deg']], | |
['translateY', [-50, '%']] | |
]; | |
*/ | |
if(!str || str == 'none') return []; | |
var functions = str.match(/[a-z]+\([^)]+\)/gi); | |
var iter = functions.length; | |
while(iter-->0) | |
{ | |
functions[iter] = functions[iter] | |
.match(/([a-z]+)\(([^,]+)(?:, ([^,]+)(?:, ([^,]+)(?:, ([^,]+))?)?)?\)/i) // Currently supports max 4 args per function | |
.filter(function sliceAndcheckUndefined(item, index) { return index && item !== undefined; }) | |
.map(function parseDataTypes(val, index, array) { | |
if(index) | |
{ | |
val = val.match(/(.*?)([a-z%]+)?$/) // (px|%|deg|s|ms|grad|rad|turn|pc|pt|in|mm|cm|em|ex|ch|rem|vw|vh|vmin|vmax) | |
.filter(function sliceAndcheckUndefined(item, index) { return index && item !== undefined; }); | |
val[0] = parseFloat(val[0]); | |
} | |
return val; | |
}); | |
} | |
return functions; | |
}, | |
identity: function(counter) { | |
var identities = []; | |
for(var iter = 0, total = counter.length; iter < total; ++iter) | |
{ | |
// Take a copy | |
var identity = counter[iter].slice(0); | |
var i = identity.length; | |
while(i-->1) identity[i] = identity[i].slice(0); | |
identities.push(identity); | |
// Set to identity | |
var name = identity[0]; | |
switch(name) | |
{ | |
case 'translate': | |
case 'translate3d': | |
case 'translateX': | |
case 'translateY': | |
case 'translateZ': | |
var i = identity.length; | |
while(i-->1) identity[i][0] = 0; | |
break; | |
case 'rotate': | |
case 'rotate3d': | |
case 'rotateX': | |
case 'rotateY': | |
case 'rotateZ': | |
var i = identity.length; | |
while(i-->1) identity[i][0] = 0; | |
break; | |
case 'scale': | |
case 'scaleX': | |
case 'scaleY': | |
case 'scaleZ': | |
var i = identity.length; | |
while(i-->1) identity[i][0] = 1; | |
break; | |
case 'skew': | |
case 'skewX': | |
case 'skewY': | |
var i = identity.length; | |
while(i-->1) identity[i][0] = 0; | |
break; | |
case 'matrix': | |
case 'matrix3d': | |
// TODO | |
break; | |
case 'perspective': | |
// TODO | |
break; | |
} | |
} | |
return identities; | |
}, | |
stringify: function(list) { | |
return list.map(function(fn) { | |
// [ 'translate', [24, 'px'], [-50, '%'] ] | |
return fn[0] + '(' + fn.slice(1).map(function(data) { return data[0] + (data[1] || ''); }).join(', ') + ')'; | |
}).join(' '); | |
}, | |
mixFunctions: function(from, to, t) { | |
// Same primitive? | |
if(from[0] == to[0]) | |
{ | |
var computed = [to[0]]; | |
// Simply interpolate values | |
for(var i = 1, j = from.length; i < j; ++i) | |
{ | |
var a = from[i]; | |
var b = to[i]; | |
var c = this.mixData(a, b, t); | |
computed.push(c); | |
} | |
return computed; | |
} | |
// Fallback to common primitive | |
else | |
{ | |
// ... | |
} | |
}, | |
mixData: function(a, b, t) { | |
// Same units | |
if(a[1] == b[1]) return [Interpolate.linear(a[0], b[0], t), b[1]]; | |
} | |
}; | |
/* | |
Test: | |
var from = 'none'; | |
var to = 'translate(20px, 150px) rotate(30deg) translateY(-50%) translate(20.5, 291.125)'; | |
console.group('Transform:'); | |
console.log('from:', Transform.stringify(Transform.parse(from))); | |
console.log('to:', Transform.stringify(Transform.parse(to))); | |
console.log('computed:', Transform.from(from).to(to).interpolate(0.5)); | |
console.groupEnd(); | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment