Skip to content

Instantly share code, notes, and snippets.

@jpommerening
Last active August 29, 2015 14:14
Show Gist options
  • Save jpommerening/3f3a7b29436bc43e30d5 to your computer and use it in GitHub Desktop.
Save jpommerening/3f3a7b29436bc43e30d5 to your computer and use it in GitHub Desktop.
CSS transform polyfill (work in progress)
;(function(window, document, Math, undefined) {
var ORIGIN_PATTERN = /\s*(-?[0-9.]+)(%?)\s+(-?[0-9.]+)(%?)\s*/;
var TRANSFORM_PATTERNS = [
/(matrix)\(\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*\)/,
/(translate)\(\s*(-?[0-9.]+)\s*,\s*(-?[0-9.]+)\s*\)/,
/(rotate|skew[XY])\(\s*(-?[0-9.]+)(deg|g?rad|turn)\s*\)/,
/(scale)\(\s*(-?[0-9.]+)(%?)\s*(?:,\s*(-?[0-9.]+)(%?)\s*)\)/
];
var TRANSFORM_FUNCTIONS = {
matrix: function (v0, v1, v2, v3, dx, dy) {
return [ v0, v1, v2, v3, dx, dy ];
},
translate: function (dx, dy) {
return [ 1, 0, 0, 1, dx, dy ];
},
rotate: function (value, unit) {
var angle = parseAngle(value, unit);
var cos = Math.cos(angle);
var sin = Math.sin(angle);
return [ cos, -sin, sin, cos, 0, 0 ];
},
skewX: function (value, unit) {
return [ 1, 0, Math.tan(parseAngle(value, unit)), 1, 0, 0 ];
},
skewY: function (value, unit) {
return [ 1, Math.tan(parseAngle(value, unit)), 0, 1, 0, 0 ];
},
scale: function (x, unitx, y, unity) {
var fx = parseFactor(x, unitx);
var fy = sy === undefined ? fx : parseFactor(y, unity);
return [ fx, 0, 0, fy, 0, 0 ];
}
};
/* simplified matrix multiplication
* for 3x3 matrices of the form
* / v0 v1 0 \
* | v2 v3 0 |
* \ dx dy 1 /
* each matrix is supplied as a 6-vector
* containing the values v0 to v3, dx and dy
*/
function mmult(M1, M2) {
return [ M1[0]*M2[0] + M1[1]*M2[2], M1[0]*M2[1] + M1[1]*M2[3],
M1[2]*M2[0] + M1[3]*M2[2], M1[2]*M2[1] + M1[3]*M2[3],
M1[4]*1 + M2[4]*1, M1[5]*1 + M2[5]*1 ];
}
function mfilter(M) {
return ( 'progid:DXImageTransform.Microsoft.Matrix('
+ 'M11=' + M[0] + ','
+ 'M12=' + M[2] + ','
+ 'M21=' + M[1] + ','
+ 'M22=' + M[3] + ',SizingMethod=\'auto expand\')' );
}
function parseAngle(value, unit) {
return {
'deg': Math.PI / 180,
'grad': Math.PI / 200,
'turn': Math.PI * 2,
'rad': 1
}[ unit ] * value;
}
function parseFactor(value, unit) {
return (unit === '%') ? value / 100 : value * 1;
}
function adjustStylePx(element, property, delta) {
element.style[property] = (delta|0 + parseInt(element.currentStyle[property], 10)|0) + 'px';
}
function adjustElementPosition(element, matrix, origin) {
var x = element.clientWidth * origin.x;
var y = element.clientHeight * origin.y;
var left = -x;
var right = element.clientWidth - x;
var top = -y;
var bottom = element.clientHeight - y;
var f = [
matrix[0] * left,
matrix[0] * right,
matrix[2] * top,
matrix[2] * bottom,
matrix[1] * left,
matrix[1] * right,
matrix[3] * top,
matrix[3] * bottom
];
var minx = Math.min(f[0], f[1]) + Math.min(f[2], f[3]);
var miny = Math.min(f[4], f[5]) + Math.min(f[6], f[7]);
var maxx = Math.max(f[0], f[1]) + Math.max(f[2], f[3]);
var maxy = Math.max(f[4], f[5]) + Math.max(f[6], f[7]);
if (element.currentStyle.position !== 'absolute') {
element.style.position = 'relative';
}
/* divide by two because IEs transform origin is always .5 .5 */
adjustStylePx(element, 'marginLeft', (minx - left)/2);
adjustStylePx(element, 'marginTop', (miny - top)/2);
adjustStylePx(element, 'marginRight', (right - maxx)/2);
adjustStylePx(element, 'marginBottom', (bottom - maxy)/2);
adjustStylePx(element, 'left', matrix[4]);
adjustStylePx(element, 'top', matrix[5]);
}
function applyTransform(rule, transform, transformOrigin) {
var i, match, offset, elements;
var matrix = [ 1, 0, 0, 1, 0, 0 ];
var origin = { x: 0.5, y: 0.5 };
for (i=0; i<TRANSFORM_PATTERNS.length; i++) {
offset = 0;
while ((match = TRANSFORM_PATTERNS[ i ].exec(transform.substr(offset)))) {
offset += match.shift().length;
matrix = mmult( matrix, TRANSFORM_FUNCTIONS[ match.shift() ].apply(null, match) );
}
}
if ((match = ORIGIN_PATTERN.exec(transformOrigin))) {
origin.x = parseFactor(match[1], match[2]);
origin.y = parseFactor(match[3], match[4]);
}
elements = document.querySelectorAll(rule.selectorText);
for (i=0; i < elements.length; i++) {
adjustElementPosition(elements[i], matrix, origin);
}
rule.style.filter = (rule.style.filter ? rule.style.filter + ', ' : '') + mfilter(matrix);
}
var styleSheets = document.styleSheets;
for (var i = 0; i < styleSheets.length; i++) {
var styleSheet = styleSheets[i];
var rules = styleSheet.rules;
for (var j = 0; j < rules.length; j++) {
var rule = rules[j];
var style = rule.style;
if (style.transform) {
applyTransform(rule, style['transform'], style['transform-origin']);
}
}
}
})(window, document, Math);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<style>
.trapeze-container {
position: relative;
}
.trapeze-container > .layer {
position: absolute;
left: 0;
right: 0;
}
.trapeze {
width: 100px;
height: 150px;
border: solid 1px #22d;
background: #ddf;
padding: 0;
margin: 0;
display: inline-block;
/* IE: simulate inline-block */
zoom: 1;
*display: inline;
}
.trapeze.left {
border-right: 0;
margin-right: -2px;
transform-origin: 0 0;
transform: matrix( 1, 0, .3, 1, 0, 0 );
}
.trapeze.right {
border-left: 0;
margin-left: -2px;
transform-origin: 0 0;
transform: matrix( 1, 0, -.3, 1, 0, 0 );
}
.trapeze.half {
background: #eef;
height: 75px;
}
</style>
</head>
<body>
<div class="trapeze-container">
<div class="layer">
<div class="trapeze left"></div>
<div class="trapeze right"></div>
</div>
<div class="layer">
<div class="trapeze left half"></div>
<div class="trapeze right half"></div>
</div>
</div>
<!--[if IE]>
<script src="cssTransform.polyfill.js"></script>
<![endif]-->
</body>
</html>
@jpommerening
Copy link
Author

Removed inline-block polyfill, fixed rounding errors.

$ uglifyjs -m -c -- cssTransform.polyfill.js | tee cssTransform.polyfill.min.js | wc -c
2241

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment