Last active
November 6, 2021 01:10
-
-
Save tmslnz/0bebcff140474e18751330dce05ae794 to your computer and use it in GitHub Desktop.
Transform stack
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
/* | |
Utility to create and manage a stack of CSS transforms. | |
Supports namespaced properties. | |
Example: | |
var el = document.querySelector('#id'); | |
var tx = new Tx(); | |
Basic usage: | |
tx | |
.translate(100, 200, 0) | |
.scale(0.5, 0.5, 0.5) | |
.render(el) | |
Namespaces | |
tx | |
.translate(100, 200, 0) | |
.translate(0, 0, 40, 'myLabel') | |
.scale(0.5, 0.5, 1) | |
.scale(1, 0.8, 1, 'myLabel') | |
.render(el); | |
=> translate3d(100px,200px,0px) | |
translate3d(0px,0px,40px) | |
scale3d(0.5,0.5,1) | |
scale3d(1,0.8,1) | |
…then later we want to change one component only: | |
tx | |
.zero('translate.myLabel', 'z') | |
.toString(); | |
=> translate3d(100px,200px,0px) | |
translate3d(0px,0px,0px) | |
scale3d(0.5,0.5,1) | |
scale3d(1,0.8,1) | |
Flattening multiple transforms: | |
tx | |
.flatten('translate') | |
.toString(); | |
=> scale3d(0.5,0.5,1) | |
scale3d(1,0.8,1) | |
translate3d(100px,200px,0px) | |
Flushing (removing) components: | |
tx | |
.flush('scale') | |
.toString(); | |
=> translate3d(100px,200px,0px) | |
*/ | |
function Tx () { | |
this.stack = []; | |
} | |
Tx.prototype.toString = function () { | |
return this.stack.reduce(function (acc, arr) { | |
var type = arr.slice(3)[0]; | |
var cmd; | |
var string; | |
switch (type) { | |
case 'translate': | |
cmd = 'translate3d'; | |
string = arr.slice(0,3).map(function (val) {return val + 'px'}).join(','); | |
break; | |
case 'scale': | |
cmd = 'scale3d'; | |
string = arr.slice(0,3).join(','); | |
break; | |
} | |
acc = acc + ' ' + cmd + '(' + string + ')'; | |
return acc; | |
}, ''); | |
}; | |
Tx.prototype.translate = function (x, y, z, label) { | |
if (arguments.length < 1) throw new Error('Missing arg from Tx.translate'); | |
var isArray = Array.isArray(x); | |
if (arguments.length < 2 && !isArray) throw new Error('Missing arg from Tx.translate'); | |
var values = isArray ? x : [x, y, z || 0]; | |
label = isArray ? y : label; | |
values.push('translate', label); | |
this.stack.push(values); | |
return this; | |
}; | |
Tx.prototype.scale = function (x, y, z, label) { | |
if (arguments.length < 1) throw new Error('Missing arg from Tx.scale'); | |
var isArray = Array.isArray(x); | |
if (arguments.length < 2 && !isArray) throw new Error('Missing arg from Tx.scale'); | |
var values = isArray ? x : [x, y, z || 0]; | |
label = isArray ? y : label; | |
values.push('scale', label); | |
this.stack.push(values); | |
return this; | |
}; | |
Tx.prototype.pop = function () { | |
this.stack.pop(); | |
return this; | |
}; | |
Tx.prototype.shift = function () { | |
this.stack.shift(); | |
return this; | |
}; | |
Tx.prototype.axisMap = { | |
'x': 0, | |
'y': 1, | |
'z': 2, | |
}; | |
Tx.prototype.flattenAll = function () { | |
var verbs = this.stack.reduce(function (acc, arr) { | |
var type = arr.slice(3)[0]; | |
if ( acc.indexOf( type ) >= 0 ) { | |
return acc; | |
} | |
acc.push(type); | |
return acc; | |
}, []); | |
verbs.forEach(this.flatten.bind(this)); | |
return this; | |
}; | |
Tx.prototype.flatten = function (verb) { | |
if (typeof verb === 'undefined') return this.flattenAll(); | |
var label = this.getNamespace(verb); | |
verb = this.getVerb(verb); | |
var accumulator; | |
switch (verb) { | |
case 'translate': | |
accumulator = [0,0,0]; | |
break; | |
case 'scale': | |
accumulator = [1,1,1]; | |
break; | |
} | |
var redux = this.stack.reduce(function (acc, arr) { | |
var type = arr.slice(3)[0]; | |
if (type !== verb) return acc; | |
if (label && label !== arr.slice(-1)[0]) return acc; | |
switch (type) { | |
case 'translate': | |
acc[0] += arr[0]; | |
acc[1] += arr[1]; | |
acc[2] += arr[2]; | |
break; | |
case 'scale': | |
acc[0] *= arr[0]; | |
acc[1] *= arr[1]; | |
acc[2] *= arr[2]; | |
break; | |
} | |
return acc; | |
}, accumulator); | |
this.flush(verb + (label ? '.' + label : '')); | |
var isZero = !redux.reduce(function (acc, val) { return acc += val }, 0); | |
if (!isZero) { | |
this[verb](redux, label); | |
} | |
return this; | |
}; | |
Tx.prototype.zero = function (verb, axis) { | |
var axisMap = this.axisMap; | |
var label = this.getNamespace(verb); | |
verb = this.getVerb(verb); | |
this.stack.forEach(function (arr) { | |
if (arr.slice(3)[0] !== verb) return; | |
if (typeof axis === 'undefined') return; | |
if (label && label !== arr.slice(-1)[0]) return; | |
arr.splice(axisMap[axis], 1, 0); | |
}); | |
return this; | |
}; | |
Tx.prototype.namespaceRegExp = /.+?\.(.+)/i; | |
Tx.prototype.verbRegExp = /^(.+?)(?=\..*|$)/i; | |
Tx.prototype.getNamespace = function (str) { | |
var match = str.split(this.namespaceRegExp); | |
if (match) { | |
return match[ 1 ]; | |
} | |
} | |
Tx.prototype.getVerb = function (str) { | |
var match = str.split(this.verbRegExp); | |
if (match) { | |
return match[ 1 ]; | |
} | |
}; | |
Tx.prototype.flush = function (verb) { | |
if (typeof verb === 'undefined') { | |
this.stack = []; | |
return this; | |
} | |
var label = this.getNamespace(verb); | |
verb = this.getVerb(verb); | |
this.stack = this.stack.filter(function (arr) { | |
var type = arr.slice(3)[0]; | |
if (label) { | |
if (label === arr.slice(-1)[0] && type === verb) return false; | |
} else { | |
if (type === verb) return false; | |
} | |
return true; | |
}); | |
return this; | |
}; | |
Tx.prototype.sort = function (order) { | |
function compare (a, b) { | |
var verbA = this.getVerb(a.slice(3)[0]); | |
var verbB = this.getVerb(b.slice(3)[0]); | |
if (order.indexOf(verbA) < order.indexOf(verbB)) return -1; | |
if (order.indexOf(verbA) > order.indexOf(verbB)) return 1; | |
return 0; | |
} | |
this.stack.sort(compare.bind(this)); | |
}; | |
Tx.prototype.render = function (el) { | |
el.style.transform = this.toString(); | |
el.style.webkitTransform = this.toString(); | |
el.style.mozTransform = this.toString(); | |
el.style.msTransform = this.toString(); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment