Skip to content

Instantly share code, notes, and snippets.

@tmslnz
Last active November 6, 2021 01:10
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tmslnz/0bebcff140474e18751330dce05ae794 to your computer and use it in GitHub Desktop.
Save tmslnz/0bebcff140474e18751330dce05ae794 to your computer and use it in GitHub Desktop.
Transform stack
/*
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