You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
551 lines
15 KiB
551 lines
15 KiB
/*
|
|
* 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 );
|