Created
August 31, 2015 10:50
This file contains 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: A jQuery cssHooks adding cross-browser 2d transform capabilities to $.fn.css() and $.fn.animate() | |
* | |
* limitations: | |
* - requires jQuery 1.4.3+ | |
* - Should you use the *translate* property, then your elements need to be absolutely positionned in a relatively positionned wrapper **or it will fail in IE678**. | |
* - transformOrigin is not accessible | |
* | |
* latest version and complete README available on Github: | |
* https://github.com/louisremi/jquery.transform.js | |
* | |
* Copyright 2011 @louis_remi | |
* Licensed under the MIT license. | |
* | |
* This saved you an hour of work? | |
* Send me music http://www.amazon.co.uk/wishlist/HNTU0468LQON | |
* | |
*/ | |
(function( $, window, document, Math, undefined ) { | |
/* | |
* Feature tests and global variables | |
*/ | |
var div = document.createElement("div"), | |
divStyle = div.style, | |
suffix = "Transform", | |
testProperties = [ | |
"O" + suffix, | |
"ms" + suffix, | |
"Webkit" + suffix, | |
"Moz" + suffix | |
], | |
i = testProperties.length, | |
supportProperty, | |
supportMatrixFilter, | |
supportFloat32Array = "Float32Array" in window, | |
propertyHook, | |
propertyGet, | |
rMatrix = /Matrix([^)]*)/, | |
rAffine = /^\s*matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*(?:,\s*0(?:px)?\s*){2}\)\s*$/, | |
_transform = "transform", | |
_transformOrigin = "transformOrigin", | |
_translate = "translate", | |
_rotate = "rotate", | |
_scale = "scale", | |
_skew = "skew", | |
_matrix = "matrix"; | |
// test different vendor prefixes of these properties | |
while ( i-- ) { | |
if ( testProperties[i] in divStyle ) { | |
$.support[_transform] = supportProperty = testProperties[i]; | |
$.support[_transformOrigin] = supportProperty + "Origin"; | |
continue; | |
} | |
} | |
// IE678 alternative | |
if ( !supportProperty ) { | |
$.support.matrixFilter = supportMatrixFilter = divStyle.filter === ""; | |
} | |
// px isn't the default unit of these properties | |
$.cssNumber[_transform] = $.cssNumber[_transformOrigin] = true; | |
/* | |
* fn.css() hooks | |
*/ | |
if ( supportProperty && supportProperty != _transform ) { | |
// Modern browsers can use jQuery.cssProps as a basic hook | |
$.cssProps[_transform] = supportProperty; | |
$.cssProps[_transformOrigin] = supportProperty + "Origin"; | |
// Firefox needs a complete hook because it stuffs matrix with "px" | |
if ( supportProperty == "Moz" + suffix ) { | |
propertyHook = { | |
get: function( elem, computed ) { | |
return (computed ? | |
// remove "px" from the computed matrix | |
$.css( elem, supportProperty ).split("px").join(""): | |
elem.style[supportProperty] | |
); | |
}, | |
set: function( elem, value ) { | |
// add "px" to matrices | |
elem.style[supportProperty] = /matrix\([^)p]*\)/.test(value) ? | |
value.replace(/matrix((?:[^,]*,){4})([^,]*),([^)]*)/, _matrix+"$1$2px,$3px"): | |
value; | |
} | |
}; | |
/* Fix two jQuery bugs still present in 1.5.1 | |
* - rupper is incompatible with IE9, see http://jqbug.com/8346 | |
* - jQuery.css is not really jQuery.cssProps aware, see http://jqbug.com/8402 | |
*/ | |
} else if ( /^1\.[0-5](?:\.|$)/.test($.fn.jquery) ) { | |
propertyHook = { | |
get: function( elem, computed ) { | |
return (computed ? | |
$.css( elem, supportProperty.replace(/^ms/, "Ms") ): | |
elem.style[supportProperty] | |
); | |
} | |
}; | |
} | |
/* TODO: leverage hardware acceleration of 3d transform in Webkit only | |
else if ( supportProperty == "Webkit" + suffix && support3dTransform ) { | |
propertyHook = { | |
set: function( elem, value ) { | |
elem.style[supportProperty] = | |
value.replace(); | |
} | |
} | |
}*/ | |
} else if ( supportMatrixFilter ) { | |
propertyHook = { | |
get: function( elem, computed, asArray ) { | |
var elemStyle = ( computed && elem.currentStyle ? elem.currentStyle : elem.style ), | |
matrix, data; | |
if ( elemStyle && rMatrix.test( elemStyle.filter ) ) { | |
matrix = RegExp.$1.split(","); | |
matrix = [ | |
matrix[0].split("=")[1], | |
matrix[2].split("=")[1], | |
matrix[1].split("=")[1], | |
matrix[3].split("=")[1] | |
]; | |
} else { | |
matrix = [1,0,0,1]; | |
} | |
if ( ! $.cssHooks[_transformOrigin] ) { | |
matrix[4] = elemStyle ? parseInt(elemStyle.left, 10) || 0 : 0; | |
matrix[5] = elemStyle ? parseInt(elemStyle.top, 10) || 0 : 0; | |
} else { | |
data = $._data( elem, "transformTranslate", undefined ); | |
matrix[4] = data ? data[0] : 0; | |
matrix[5] = data ? data[1] : 0; | |
} | |
return asArray ? matrix : _matrix+"(" + matrix + ")"; | |
}, | |
set: function( elem, value, animate ) { | |
var elemStyle = elem.style, | |
currentStyle, | |
Matrix, | |
filter, | |
centerOrigin; | |
if ( !animate ) { | |
elemStyle.zoom = 1; | |
} | |
value = matrix(value); | |
// rotate, scale and skew | |
Matrix = [ | |
"Matrix("+ | |
"M11="+value[0], | |
"M12="+value[2], | |
"M21="+value[1], | |
"M22="+value[3], | |
"SizingMethod='auto expand'" | |
].join(); | |
filter = ( currentStyle = elem.currentStyle ) && currentStyle.filter || elemStyle.filter || ""; | |
elemStyle.filter = rMatrix.test(filter) ? | |
filter.replace(rMatrix, Matrix) : | |
filter + " progid:DXImageTransform.Microsoft." + Matrix + ")"; | |
if ( ! $.cssHooks[_transformOrigin] ) { | |
// center the transform origin, from pbakaus's Transformie http://github.com/pbakaus/transformie | |
if ( (centerOrigin = $.transform.centerOrigin) ) { | |
elemStyle[centerOrigin == "margin" ? "marginLeft" : "left"] = -(elem.offsetWidth/2) + (elem.clientWidth/2) + "px"; | |
elemStyle[centerOrigin == "margin" ? "marginTop" : "top"] = -(elem.offsetHeight/2) + (elem.clientHeight/2) + "px"; | |
} | |
// translate | |
// We assume that the elements are absolute positionned inside a relative positionned wrapper | |
elemStyle.left = value[4] + "px"; | |
elemStyle.top = value[5] + "px"; | |
} else { | |
$.cssHooks[_transformOrigin].set( elem, value ); | |
} | |
} | |
}; | |
} | |
// populate jQuery.cssHooks with the appropriate hook if necessary | |
if ( propertyHook ) { | |
$.cssHooks[_transform] = propertyHook; | |
} | |
// we need a unique setter for the animation logic | |
propertyGet = propertyHook && propertyHook.get || $.css; | |
/* | |
* fn.animate() hooks | |
*/ | |
$.fx.step.transform = function( fx ) { | |
var elem = fx.elem, | |
start = fx.start, | |
end = fx.end, | |
pos = fx.pos, | |
transform = "", | |
precision = 1E5, | |
i, startVal, endVal, unit; | |
// fx.end and fx.start need to be converted to interpolation lists | |
if ( !start || typeof start === "string" ) { | |
// the following block can be commented out with jQuery 1.5.1+, see #7912 | |
if ( !start ) { | |
start = propertyGet( elem, supportProperty ); | |
} | |
// force layout only once per animation | |
if ( supportMatrixFilter ) { | |
elem.style.zoom = 1; | |
} | |
// replace "+=" in relative animations (-= is meaningless with transforms) | |
end = end.split("+=").join(start); | |
// parse both transform to generate interpolation list of same length | |
$.extend( fx, interpolationList( start, end ) ); | |
start = fx.start; | |
end = fx.end; | |
} | |
i = start.length; | |
// interpolate functions of the list one by one | |
while ( i-- ) { | |
startVal = start[i]; | |
endVal = end[i]; | |
unit = +false; | |
switch ( startVal[0] ) { | |
case _translate: | |
unit = "px"; | |
case _scale: | |
unit || ( unit = ""); | |
transform = startVal[0] + "(" + | |
Math.round( (startVal[1][0] + (endVal[1][0] - startVal[1][0]) * pos) * precision ) / precision + unit +","+ | |
Math.round( (startVal[1][1] + (endVal[1][1] - startVal[1][1]) * pos) * precision ) / precision + unit + ")"+ | |
transform; | |
break; | |
case _skew + "X": | |
case _skew + "Y": | |
case _rotate: | |
transform = startVal[0] + "(" + | |
Math.round( (startVal[1] + (endVal[1] - startVal[1]) * pos) * precision ) / precision +"rad)"+ | |
transform; | |
break; | |
} | |
} | |
fx.origin && ( transform = fx.origin + transform ); | |
propertyHook && propertyHook.set ? | |
propertyHook.set( elem, transform, +true ): | |
elem.style[supportProperty] = transform; | |
}; | |
/* | |
* Utility functions | |
*/ | |
// turns a transform string into its "matrix(A,B,C,D,X,Y)" form (as an array, though) | |
function matrix( transform ) { | |
transform = transform.split(")"); | |
var | |
trim = $.trim | |
, i = -1 | |
// last element of the array is an empty string, get rid of it | |
, l = transform.length -1 | |
, split, prop, val | |
, prev = supportFloat32Array ? new Float32Array(6) : [] | |
, curr = supportFloat32Array ? new Float32Array(6) : [] | |
, rslt = supportFloat32Array ? new Float32Array(6) : [1,0,0,1,0,0] | |
; | |
prev[0] = prev[3] = rslt[0] = rslt[3] = 1; | |
prev[1] = prev[2] = prev[4] = prev[5] = 0; | |
// Loop through the transform properties, parse and multiply them | |
while ( ++i < l ) { | |
split = transform[i].split("("); | |
prop = trim(split[0]); | |
val = split[1]; | |
curr[0] = curr[3] = 1; | |
curr[1] = curr[2] = curr[4] = curr[5] = 0; | |
switch (prop) { | |
case _translate+"X": | |
curr[4] = parseInt(val, 10); | |
break; | |
case _translate+"Y": | |
curr[5] = parseInt(val, 10); | |
break; | |
case _translate: | |
val = val.split(","); | |
curr[4] = parseInt(val[0], 10); | |
curr[5] = parseInt(val[1] || 0, 10); | |
break; | |
case _rotate: | |
val = toRadian(val); | |
curr[0] = Math.cos(val); | |
curr[1] = Math.sin(val); | |
curr[2] = -Math.sin(val); | |
curr[3] = Math.cos(val); | |
break; | |
case _scale+"X": | |
curr[0] = +val; | |
break; | |
case _scale+"Y": | |
curr[3] = val; | |
break; | |
case _scale: | |
val = val.split(","); | |
curr[0] = val[0]; | |
curr[3] = val.length>1 ? val[1] : val[0]; | |
break; | |
case _skew+"X": | |
curr[2] = Math.tan(toRadian(val)); | |
break; | |
case _skew+"Y": | |
curr[1] = Math.tan(toRadian(val)); | |
break; | |
case _matrix: | |
val = val.split(","); | |
curr[0] = val[0]; | |
curr[1] = val[1]; | |
curr[2] = val[2]; | |
curr[3] = val[3]; | |
curr[4] = parseInt(val[4], 10); | |
curr[5] = parseInt(val[5], 10); | |
break; | |
} | |
// Matrix product (array in column-major order) | |
rslt[0] = prev[0] * curr[0] + prev[2] * curr[1]; | |
rslt[1] = prev[1] * curr[0] + prev[3] * curr[1]; | |
rslt[2] = prev[0] * curr[2] + prev[2] * curr[3]; | |
rslt[3] = prev[1] * curr[2] + prev[3] * curr[3]; | |
rslt[4] = prev[0] * curr[4] + prev[2] * curr[5] + prev[4]; | |
rslt[5] = prev[1] * curr[4] + prev[3] * curr[5] + prev[5]; | |
prev = [rslt[0],rslt[1],rslt[2],rslt[3],rslt[4],rslt[5]]; | |
} | |
return rslt; | |
} | |
// turns a matrix into its rotate, scale and skew components | |
// algorithm from http://hg.mozilla.org/mozilla-central/file/7cb3e9795d04/layout/style/nsStyleAnimation.cpp | |
function unmatrix(matrix) { | |
var | |
scaleX | |
, scaleY | |
, skew | |
, A = matrix[0] | |
, B = matrix[1] | |
, C = matrix[2] | |
, D = matrix[3] | |
; | |
// Make sure matrix is not singular | |
if ( A * D - B * C ) { | |
// step (3) | |
scaleX = Math.sqrt( A * A + B * B ); | |
A /= scaleX; | |
B /= scaleX; | |
// step (4) | |
skew = A * C + B * D; | |
C -= A * skew; | |
D -= B * skew; | |
// step (5) | |
scaleY = Math.sqrt( C * C + D * D ); | |
C /= scaleY; | |
D /= scaleY; | |
skew /= scaleY; | |
// step (6) | |
if ( A * D < B * C ) { | |
A = -A; | |
B = -B; | |
skew = -skew; | |
scaleX = -scaleX; | |
} | |
// matrix is singular and cannot be interpolated | |
} else { | |
// In this case the elem shouldn't be rendered, hence scale == 0 | |
scaleX = scaleY = skew = 0; | |
} | |
// The recomposition order is very important | |
// see http://hg.mozilla.org/mozilla-central/file/7cb3e9795d04/layout/style/nsStyleAnimation.cpp#l971 | |
return [ | |
[_translate, [+matrix[4], +matrix[5]]], | |
[_rotate, Math.atan2(B, A)], | |
[_skew + "X", Math.atan(skew)], | |
[_scale, [scaleX, scaleY]] | |
]; | |
} | |
// build the list of transform functions to interpolate | |
// use the algorithm described at http://dev.w3.org/csswg/css3-2d-transforms/#animation | |
function interpolationList( start, end ) { | |
var list = { | |
start: [], | |
end: [] | |
}, | |
i = -1, l, | |
currStart, currEnd, currType; | |
// get rid of affine transform matrix | |
( start == "none" || isAffine( start ) ) && ( start = "" ); | |
( end == "none" || isAffine( end ) ) && ( end = "" ); | |
// if end starts with the current computed style, this is a relative animation | |
// store computed style as the origin, remove it from start and end | |
if ( start && end && !end.indexOf("matrix") && toArray( start ).join() == toArray( end.split(")")[0] ).join() ) { | |
list.origin = start; | |
start = ""; | |
end = end.slice( end.indexOf(")") +1 ); | |
} | |
if ( !start && !end ) { return; } | |
// start or end are affine, or list of transform functions are identical | |
// => functions will be interpolated individually | |
if ( !start || !end || functionList(start) == functionList(end) ) { | |
start && ( start = start.split(")") ) && ( l = start.length ); | |
end && ( end = end.split(")") ) && ( l = end.length ); | |
while ( ++i < l-1 ) { | |
start[i] && ( currStart = start[i].split("(") ); | |
end[i] && ( currEnd = end[i].split("(") ); | |
currType = $.trim( ( currStart || currEnd )[0] ); | |
append( list.start, parseFunction( currType, currStart ? currStart[1] : 0 ) ); | |
append( list.end, parseFunction( currType, currEnd ? currEnd[1] : 0 ) ); | |
} | |
// otherwise, functions will be composed to a single matrix | |
} else { | |
list.start = unmatrix(matrix(start)); | |
list.end = unmatrix(matrix(end)) | |
} | |
return list; | |
} | |
function parseFunction( type, value ) { | |
var | |
// default value is 1 for scale, 0 otherwise | |
defaultValue = +(!type.indexOf(_scale)), | |
scaleX, | |
// remove X/Y from scaleX/Y & translateX/Y, not from skew | |
cat = type.replace( /e[XY]/, "e" ); | |
switch ( type ) { | |
case _translate+"Y": | |
case _scale+"Y": | |
value = [ | |
defaultValue, | |
value ? | |
parseFloat( value ): | |
defaultValue | |
]; | |
break; | |
case _translate+"X": | |
case _translate: | |
case _scale+"X": | |
scaleX = 1; | |
case _scale: | |
value = value ? | |
( value = value.split(",") ) && [ | |
parseFloat( value[0] ), | |
parseFloat( value.length>1 ? value[1] : type == _scale ? scaleX || value[0] : defaultValue+"" ) | |
]: | |
[defaultValue, defaultValue]; | |
break; | |
case _skew+"X": | |
case _skew+"Y": | |
case _rotate: | |
value = value ? toRadian( value ) : 0; | |
break; | |
case _matrix: | |
return unmatrix( value ? toArray(value) : [1,0,0,1,0,0] ); | |
break; | |
} | |
return [[ cat, value ]]; | |
} | |
function isAffine( matrix ) { | |
return rAffine.test(matrix); | |
} | |
function functionList( transform ) { | |
return transform.replace(/(?:\([^)]*\))|\s/g, ""); | |
} | |
function append( arr1, arr2, value ) { | |
while ( value = arr2.shift() ) { | |
arr1.push( value ); | |
} | |
} | |
// converts an angle string in any unit to a radian Float | |
function toRadian(value) { | |
return ~value.indexOf("deg") ? | |
parseInt(value,10) * (Math.PI * 2 / 360): | |
~value.indexOf("grad") ? | |
parseInt(value,10) * (Math.PI/200): | |
parseFloat(value); | |
} | |
// Converts "matrix(A,B,C,D,X,Y)" to [A,B,C,D,X,Y] | |
function toArray(matrix) { | |
// remove the unit of X and Y for Firefox | |
matrix = /([^,]*),([^,]*),([^,]*),([^,]*),([^,p]*)(?:px)?,([^)p]*)(?:px)?/.exec(matrix); | |
return [matrix[1], matrix[2], matrix[3], matrix[4], matrix[5], matrix[6]]; | |
} | |
$.transform = { | |
centerOrigin: "margin" | |
}; | |
})( jQuery, window, document, Math ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment