Skip to content

Instantly share code, notes, and snippets.

@Martin-Pitt
Created June 4, 2015 09:07
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 Martin-Pitt/3a6f2168a03a7502575a to your computer and use it in GitHub Desktop.
Save Martin-Pitt/3a6f2168a03a7502575a to your computer and use it in GitHub Desktop.
/// 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