From b1053582a0c231632d94e5fd17b5f060b67e3eda Mon Sep 17 00:00:00 2001 From: heygrady Date: Thu, 1 Dec 2011 11:57:03 -0800 Subject: [PATCH 01/10] correcting the matrix multiplication, some values were transposed because of a confusion between row-major and column-major. first swag at adding in full support for origin and translation. --- jquery.transform2d.js | 230 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 202 insertions(+), 28 deletions(-) diff --git a/jquery.transform2d.js b/jquery.transform2d.js index d33c48c..526f3df 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -110,9 +110,14 @@ if ( supportProperty && supportProperty != propertyName ) { }*/ } else if ( supportMatrixFilter ) { + var translateX = suffix + '-translate-x', + translateY = suffix + '-translate-y' + ; + propertyHook = { get: function( elem, computed ) { - var elemStyle = ( computed && elem.currentStyle ? elem.currentStyle : elem.style ), + var $elem = $(elem), + elemStyle = ( computed && elem.currentStyle ? elem.currentStyle : elem.style ), matrix; if ( elemStyle && rMatrix.test( elemStyle.filter ) ) { @@ -126,12 +131,13 @@ if ( supportProperty && supportProperty != propertyName ) { } else { matrix = [1,0,0,1]; } - matrix[4] = elemStyle ? elemStyle.left : 0; - matrix[5] = elemStyle ? elemStyle.top : 0; + matrix[4] = $elem.data(translateX) || 0; + matrix[5] = $elem.data(translateY) || 0; return _matrix+"(" + matrix + ")"; }, set: function( elem, value, animate ) { - var elemStyle = elem.style, + var $elem = $(elem), + elemStyle = elem.style, currentStyle, Matrix, filter, @@ -159,21 +165,146 @@ if ( supportProperty && supportProperty != propertyName ) { filter.replace(rMatrix, Matrix) : filter + " progid:DXImageTransform.Microsoft." + Matrix + ")"; - // 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 - //if ( !animate || animate.T ) { - // We assume that the elements are absolute positionned inside a relative positionned wrapper - elemStyle.left = value[4] + "px"; - elemStyle.top = value[5] + "px"; + // remember the translation for later + $elem.data(translateX, value[4]); + $elem.data(translateY, value[5]); + + // fake the origin + $.cssHooks.transformOrigin.set(elem); //} } }; + + + // handle transform-origin + var rperc = /%/, + originSuffix = 'transform-origin' + ; + $.cssHooks.transformOrigin = { + get: function( elem, computed ) { + var $elem = $(elem), + origin = $elem.data(originSuffix) + ; + + // try to look it up in the existing CSS + if (!origin) { + var testProperties = [ + '-o-' + originSuffix, + '-moz-' + originSuffix, + '-webkit-' + originSuffix, + '-ms-' + originSuffix, + originSuffix + ], + i = testProperties.length, + currStyle = elem.currentStyle + ; + + while ( i-- ) { + if ( testProperties[i] in currStyle ) { + origin = currStyle[testProperties[i]]; + $elem.data(originSuffix, origin); + break; + } + } + } + + // otherwise use the default + if (!origin) { + origin = 'center center'; + $elem.data(originSuffix, origin); + } + + return origin; + }, + + set: function( elem, value, animate ) { + var $elem = $(elem), + transform = $elem.css('transform') + ; + + // save it if there's a new value + // NOTE: undefined means we're trying to set a transform and need to handle translation + if (value === undefined) { $elem.data(originSuffix, value) } + + // if there's no transform, don't do anything + if (!transform) { + return; + } + + // convert the transform into a useful array + transform = matrix(transform); + + // fake the origin with some fancy css + // we also fake the translation here + var tx = transform[4] || $elem.data(translateX) || 0, + ty = transform[5] || $elem.data(translateY) || 0, + cssPosition = $elem.css('position'), + origin = (value === undefined ? $.cssHooks.transformOrigin.get(elem) : value).split(' ') + ; + + // correct for keyword lengths + switch (origin[0]) { + case 'left': origin[0] = '0'; break; + case 'right': origin[0] = '100%'; break; + case 'center': // no break + case undefined: origin[0] = '50%'; + } + switch (origin[1]) { + case 'top': origin[1] = '0'; break; + case 'bottom': origin[1] = '100%'; break; + case 'center': // no break + case undefined: origin[1] = '50%'; //TODO: does this work? + } + + // calculate and return the correct size + //////////////////////////////////////////////////////////////// + // find the real height of the original object + var ratio = transformOffset(transform, 1, 1), + width = $elem.outerWidth() / ratio.width, + height = $elem.outerHeight() / ratio.height + ; + + // turn the origin into unitless pixels + // TODO: correct for non-px lengths + // TODO: ems would be easy + origin[0] = rperc.test(origin[0]) ? parseFloat(origin[0])/100*width : parseFloat(origin[0]); + origin[1] = rperc.test(origin[1]) ? parseFloat(origin[1])/100*height : parseFloat(origin[1]); + + // find the origin offset + var toCenter = transformVector(transform,origin[0], origin[1]), + fromCenter = transformVector(transform, 0, 0), + offset = { + top: (fromCenter[1]) - (toCenter[1] - origin[1]), + left: (fromCenter[0]) - (toCenter[0] - origin[0]) + }, + // IE glues the top-most and left-most pixels of the transformed object to top/left of the original object + sides = transformSides(transform, width, height) + ; + /////////////////////////////////////////////////////////////// + + //TODO: if the element is already positioned, we should attempt to respect it (somehow) + + // apply the css + var css, + top = offset.top + ty + sides.top, + left = offset.left + tx + sides.left + ; + + if (cssPosition === 'absolute' || cssPosition === 'fixed') { + css = { + marginTop: top, + marginLeft: left + } + } else { + css = { + position: 'relative', + top: top, + left: left + } + } + $elem.css(css); + } + }; } // populate jQuery.cssHooks with the appropriate hook if necessary if ( propertyHook ) { @@ -253,12 +384,56 @@ $.fx.step.transform = function( fx ) { /* * Utility functions */ +// convert a vector +function transformVector(a, x, y) { + return [ + a[0] * x + a[2] * y, + a[1] * x + a[3] * y + ]; +} + +// calculate the corner vectors +function transformCorners(a, x, y) { + return [ + /* tl */ transformVector(a, 0, 0), + /* bl */ transformVector(a, 0, y), + /* tr */ transformVector(a, x, 0), + /* br */ transformVector(a, x, y) + ]; +} + +// measure the length of the sides +// TODO: arrays are faster than objects +function transformSides(a, x, y) { + // The corners of the box + var c = transformCorners(a, x, y); + + return { + top: Math.min(c[0][1], c[2][1], c[3][1], c[1][1]), + bottom: Math.max(c[0][1], c[2][1], c[3][1], c[1][1]), + left: Math.min(c[0][0], c[2][0], c[3][0], c[1][0]), + right: Math.max(c[0][0], c[2][0], c[3][0], c[1][0]) + }; +} + +// measure the offset height and width +// TODO: arrays are faster than objects +function transformOffset(a, x, y) { + // The sides of the box + var s = transformSides(a, x, y); + + // return offset + return { + height: Math.abs(s.bottom - s.top), + width: Math.abs(s.right - s.left) + }; +} // turns a transform string into its "matrix(A,B,C,D,X,Y)" form (as an array, though) +// column-major order function matrix( transform ) { transform = transform.split(")"); - var - trim = $.trim + var trim = $.trim , i = -1 // last element of the array is an empty string, get rid of it , l = transform.length -1 @@ -267,7 +442,7 @@ function matrix( transform ) { , 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; @@ -341,13 +516,13 @@ function matrix( transform ) { break; } - // Matrix product - rslt[0] = prev[0] * curr[0] + prev[1] * curr[2]; - rslt[1] = prev[0] * curr[1] + prev[1] * curr[3]; - rslt[2] = prev[2] * curr[0] + prev[3] * curr[2]; - rslt[3] = prev[2] * curr[1] + prev[3] * curr[3]; - rslt[4] = prev[2] * curr[5] + prev[3] * curr[4] + prev[4]; - rslt[5] = prev[0] * curr[5] + prev[1] * curr[4] + prev[5]; + // Matrix product (array is 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]]; } @@ -357,8 +532,7 @@ function matrix( transform ) { // 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 + var scaleX , scaleY , skew , A = matrix[0] From 6ff1f21c81b52c9682150cfa3846fd387249ee93 Mon Sep 17 00:00:00 2001 From: heygrady Date: Thu, 1 Dec 2011 15:45:36 -0800 Subject: [PATCH 02/10] - changing default centerOrigin to "position" instead of "margin" - cleaning up some comments - supporting existing positioning for position relative elements better --- jquery.transform2d.js | 54 ++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 16 deletions(-) diff --git a/jquery.transform2d.js b/jquery.transform2d.js index 526f3df..2e23ca5 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -238,11 +238,11 @@ if ( supportProperty && supportProperty != propertyName ) { // we also fake the translation here var tx = transform[4] || $elem.data(translateX) || 0, ty = transform[5] || $elem.data(translateY) || 0, - cssPosition = $elem.css('position'), origin = (value === undefined ? $.cssHooks.transformOrigin.get(elem) : value).split(' ') ; // correct for keyword lengths + // TODO: the keywords are allowed to be transposed switch (origin[0]) { case 'left': origin[0] = '0'; break; case 'right': origin[0] = '100%'; break; @@ -253,12 +253,13 @@ if ( supportProperty && supportProperty != propertyName ) { case 'top': origin[1] = '0'; break; case 'bottom': origin[1] = '100%'; break; case 'center': // no break - case undefined: origin[1] = '50%'; //TODO: does this work? + case undefined: origin[1] = '50%'; } - // calculate and return the correct size - //////////////////////////////////////////////////////////////// - // find the real height of the original object + // calculate and return the correct size + // find the real size of the original object + // (IE reports the size of the transformed object) + // the ratio is basically the transformed size of 1x1 object var ratio = transformOffset(transform, 1, 1), width = $elem.outerWidth() / ratio.width, height = $elem.outerHeight() / ratio.height @@ -266,40 +267,61 @@ if ( supportProperty && supportProperty != propertyName ) { // turn the origin into unitless pixels // TODO: correct for non-px lengths - // TODO: ems would be easy + // TODO: ems would be easy enough to handle origin[0] = rperc.test(origin[0]) ? parseFloat(origin[0])/100*width : parseFloat(origin[0]); origin[1] = rperc.test(origin[1]) ? parseFloat(origin[1])/100*height : parseFloat(origin[1]); // find the origin offset - var toCenter = transformVector(transform,origin[0], origin[1]), + var toCenter = transformVector(transform, origin[0], origin[1]), fromCenter = transformVector(transform, 0, 0), offset = { top: (fromCenter[1]) - (toCenter[1] - origin[1]), left: (fromCenter[0]) - (toCenter[0] - origin[0]) }, - // IE glues the top-most and left-most pixels of the transformed object to top/left of the original object sides = transformSides(transform, width, height) ; - /////////////////////////////////////////////////////////////// - - //TODO: if the element is already positioned, we should attempt to respect it (somehow) // apply the css - var css, + var cssPosition = $elem.css('position'), + css, top = offset.top + ty + sides.top, left = offset.left + tx + sides.left ; - if (cssPosition === 'absolute' || cssPosition === 'fixed') { + if ($.transform.centerOrigin === 'margin' || cssPosition === 'absolute' || cssPosition === 'fixed') { + // use margin positioning for positioned elements css = { marginTop: top, marginLeft: left } } else { + // try to respect an existing top/left if it's in the CSS + var cssTop = 0, + cssLeft = 0 + ; + + // look it up for position relative items + if (cssPosition !== 'static') { + // blank out the inline styles, we're going to overwrite them anyway + elem.style.top = null; + elem.style.left = null; + + // look up the CSS styles + var currentTop = elem.currentStyle.top, + currentLeft = elem.currentStyle.left + ; + + // if they're not 'auto' then use those + // TODO: handle non-pixel units and percentages + if (currentTop !== 'auto') { cssTop = parseInt(currentTop, 10); } + if (currentLeft !== 'auto') { cssLeft = parseInt(currentLeft, 10); } + } + + // use relative positioning when possible; it's the most like the CSS3 spec. css = { position: 'relative', - top: top, - left: left + top: top + cssTop, + left: left + cssLeft } } $elem.css(css); @@ -711,7 +733,7 @@ function toArray(matrix) { } $.transform = { - centerOrigin: "margin" + centerOrigin: "position" }; })( jQuery, window, document, Math ); From 5ecb4586f0f764e8efc9089bd23fd60d37ed2635 Mon Sep 17 00:00:00 2001 From: heygrady Date: Thu, 1 Dec 2011 16:05:10 -0800 Subject: [PATCH 03/10] respecting existing margins when using margin for origin faking --- jquery.transform2d.js | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/jquery.transform2d.js b/jquery.transform2d.js index 2e23ca5..6e1be07 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -178,7 +178,7 @@ if ( supportProperty && supportProperty != propertyName ) { // handle transform-origin var rperc = /%/, - originSuffix = 'transform-origin' + originSuffix = propertyName + '-origin' ; $.cssHooks.transformOrigin = { get: function( elem, computed ) { @@ -219,7 +219,7 @@ if ( supportProperty && supportProperty != propertyName ) { set: function( elem, value, animate ) { var $elem = $(elem), - transform = $elem.css('transform') + transform = $elem.css(propertyName) ; // save it if there's a new value @@ -285,31 +285,46 @@ if ( supportProperty && supportProperty != propertyName ) { var cssPosition = $elem.css('position'), css, top = offset.top + ty + sides.top, - left = offset.left + tx + sides.left + left = offset.left + tx + sides.left, + cssTop = 0, + cssLeft = 0, + currentTop, + currentLeft, + elemStyle = elem.style, + currStyle = elem.currentStyle ; if ($.transform.centerOrigin === 'margin' || cssPosition === 'absolute' || cssPosition === 'fixed') { + // try to respect an existing marginTop/Left if it's in the CSS + // blank out the inline styles, we're going to overwrite them anyway + elemStyle.marginTop = null; + elemStyle.marginLeft = null; + + // look up the CSS styles + currentTop = currStyle.marginTop; + currentLeft = currStyle.marginLeft; + + // if they're not 'auto' then use those + // TODO: handle non-pixel units and percentages + if (currentTop !== 'auto') { cssTop = parseInt(currentTop, 10); } + if (currentLeft !== 'auto') { cssLeft = parseInt(currentLeft, 10); } + // use margin positioning for positioned elements css = { - marginTop: top, - marginLeft: left + marginTop: top + cssTop, + marginLeft: left + cssLeft } } else { // try to respect an existing top/left if it's in the CSS - var cssTop = 0, - cssLeft = 0 - ; - // look it up for position relative items if (cssPosition !== 'static') { // blank out the inline styles, we're going to overwrite them anyway - elem.style.top = null; - elem.style.left = null; + elemStyle.top = null; + elemStyle.left = null; // look up the CSS styles - var currentTop = elem.currentStyle.top, - currentLeft = elem.currentStyle.left - ; + currentTop = currStyle.top; + currentLeft = currStyle.left; // if they're not 'auto' then use those // TODO: handle non-pixel units and percentages From ac4b612584bb944b647d4473f5933edc68b68f32 Mon Sep 17 00:00:00 2001 From: heygrady Date: Tue, 6 Dec 2011 17:43:05 -0800 Subject: [PATCH 04/10] starting on adding animation support for transform origin (for laffs) --- jquery.transform2d.js | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/jquery.transform2d.js b/jquery.transform2d.js index 6e1be07..f0dc46a 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -16,7 +16,7 @@ * Send me music http://www.amazon.co.uk/wishlist/HNTU0468LQON * */ -(function( $, window, document, Math ) { +(function( $, window, document, Math, undefined ) { "use strict"; /* @@ -25,8 +25,10 @@ var div = document.createElement("div"), divStyle = div.style, propertyName = "transform", + originPropertyName = propertyName + '-origin', suffix = "Transform", testProperties = [ + propertyName, "O" + suffix, "ms" + suffix, "Webkit" + suffix, @@ -40,6 +42,7 @@ var div = document.createElement("div"), propertyGet, rMatrix = /Matrix([^)]*)/, rAffine = /^\s*matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*(?:,\s*0(?:px)?\s*){2}\)\s*$/, + rperc = /%/, _translate = "translate", _rotate = "rotate", _scale = "scale", @@ -60,6 +63,7 @@ if ( !supportProperty ) { // px isn't the default unit of this property $.cssNumber[propertyName] = true; +$.cssNumber[originPropertyName] = true; /* * fn.css() hooks @@ -177,32 +181,32 @@ if ( supportProperty && supportProperty != propertyName ) { // handle transform-origin - var rperc = /%/, - originSuffix = propertyName + '-origin' - ; $.cssHooks.transformOrigin = { get: function( elem, computed ) { + // TODO: handle computed var $elem = $(elem), - origin = $elem.data(originSuffix) + origin = $elem.data(originPropertyName) ; // try to look it up in the existing CSS if (!origin) { + // ordered backwards because we loop backwards var testProperties = [ - '-o-' + originSuffix, - '-moz-' + originSuffix, - '-webkit-' + originSuffix, - '-ms-' + originSuffix, - originSuffix + '-o-' + originPropertyName, + '-moz-' + originPropertyName, + '-webkit-' + originPropertyName, + '-ms-' + originPropertyName, + originPropertyName ], i = testProperties.length, currStyle = elem.currentStyle ; + // loop backwards while ( i-- ) { if ( testProperties[i] in currStyle ) { origin = currStyle[testProperties[i]]; - $elem.data(originSuffix, origin); + $elem.data(originPropertyName, origin); break; } } @@ -211,7 +215,7 @@ if ( supportProperty && supportProperty != propertyName ) { // otherwise use the default if (!origin) { origin = 'center center'; - $elem.data(originSuffix, origin); + $elem.data(originPropertyName, origin); } return origin; @@ -224,7 +228,7 @@ if ( supportProperty && supportProperty != propertyName ) { // save it if there's a new value // NOTE: undefined means we're trying to set a transform and need to handle translation - if (value === undefined) { $elem.data(originSuffix, value) } + if (value !== undefined) { $elem.data(originPropertyName, value) } // if there's no transform, don't do anything if (!transform) { @@ -309,7 +313,7 @@ if ( supportProperty && supportProperty != propertyName ) { if (currentTop !== 'auto') { cssTop = parseInt(currentTop, 10); } if (currentLeft !== 'auto') { cssLeft = parseInt(currentLeft, 10); } - // use margin positioning for positioned elements + // use margin positioning for positioned elements, the margins won't really negatively affect anything (get it? negative?) css = { marginTop: top + cssTop, marginLeft: left + cssLeft @@ -418,6 +422,14 @@ $.fx.step.transform = function( fx ) { elem.style[supportProperty] = transform; }; + +/* + * fn.animate() hooks for transform-origin + */ +$.fx.step.transformOrigin = function( fx ) { + +} + /* * Utility functions */ From 8cd751fd06df076124cad5ec75d9518f6ef25e9f Mon Sep 17 00:00:00 2001 From: heygrady Date: Thu, 15 Dec 2011 16:20:34 -0800 Subject: [PATCH 05/10] supporting transform-origin and transform-origin animation. --- jquery.transform2d.js | 243 +++++++++++++++++++++++++++++++++++------- 1 file changed, 205 insertions(+), 38 deletions(-) diff --git a/jquery.transform2d.js b/jquery.transform2d.js index f0dc46a..de1641a 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -25,7 +25,6 @@ var div = document.createElement("div"), divStyle = div.style, propertyName = "transform", - originPropertyName = propertyName + '-origin', suffix = "Transform", testProperties = [ propertyName, @@ -34,14 +33,22 @@ var div = document.createElement("div"), "Webkit" + suffix, "Moz" + suffix ], + originSuffix = 'Origin', + originPropertyCssName = propertyName + '-origin', + originPropertyName = propertyName + originSuffix, i = testProperties.length, supportProperty, + supportOriginProperty, supportMatrixFilter, supportFloat32Array = "Float32Array" in window, propertyHook, propertyGet, + originPropertyHook, + originPropertyGet, + originPropertySet, rMatrix = /Matrix([^)]*)/, rAffine = /^\s*matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*(?:,\s*0(?:px)?\s*){2}\)\s*$/, + runits = /^([\+\-]=)?(-?[\d+\.\-]+)([a-z]+|%)?(.*?)$/i, rperc = /%/, _translate = "translate", _rotate = "rotate", @@ -56,6 +63,8 @@ while ( i-- ) { continue; } } +supportOriginProperty = supportProperty + originSuffix; + // IE678 alternative if ( !supportProperty ) { $.support.matrixFilter = supportMatrixFilter = divStyle.filter === ""; @@ -71,6 +80,7 @@ $.cssNumber[originPropertyName] = true; if ( supportProperty && supportProperty != propertyName ) { // Modern browsers can use jQuery.cssProps as a basic hook $.cssProps[propertyName] = supportProperty; + $.cssProps[originPropertyName] = supportOriginProperty; // Firefox needs a complete hook because it stuffs matrix with "px" if ( supportProperty == "Moz" + suffix ) { @@ -174,29 +184,29 @@ if ( supportProperty && supportProperty != propertyName ) { $elem.data(translateY, value[5]); // fake the origin - $.cssHooks.transformOrigin.set(elem); + originPropertySet(elem); //} } }; // handle transform-origin - $.cssHooks.transformOrigin = { + originPropertyHook = { get: function( elem, computed ) { // TODO: handle computed var $elem = $(elem), - origin = $elem.data(originPropertyName) + origin = $elem.data(originPropertyCssName) ; // try to look it up in the existing CSS if (!origin) { // ordered backwards because we loop backwards var testProperties = [ - '-o-' + originPropertyName, - '-moz-' + originPropertyName, - '-webkit-' + originPropertyName, - '-ms-' + originPropertyName, - originPropertyName + '-o-' + originPropertyCssName, + '-moz-' + originPropertyCssName, + '-webkit-' + originPropertyCssName, + '-ms-' + originPropertyCssName, + originPropertyCssName ], i = testProperties.length, currStyle = elem.currentStyle @@ -206,7 +216,7 @@ if ( supportProperty && supportProperty != propertyName ) { while ( i-- ) { if ( testProperties[i] in currStyle ) { origin = currStyle[testProperties[i]]; - $elem.data(originPropertyName, origin); + $elem.data(originPropertyCssName, origin); break; } } @@ -214,8 +224,8 @@ if ( supportProperty && supportProperty != propertyName ) { // otherwise use the default if (!origin) { - origin = 'center center'; - $elem.data(originPropertyName, origin); + origin = '50% 50%'; // use percentages instead of keywords + $elem.data(originPropertyCssName, origin); } return origin; @@ -223,12 +233,12 @@ if ( supportProperty && supportProperty != propertyName ) { set: function( elem, value, animate ) { var $elem = $(elem), - transform = $elem.css(propertyName) + transform = propertyGet(elem) ; // save it if there's a new value // NOTE: undefined means we're trying to set a transform and need to handle translation - if (value !== undefined) { $elem.data(originPropertyName, value) } + if (value !== undefined) { $elem.data(originPropertyCssName, value) } // if there's no transform, don't do anything if (!transform) { @@ -242,23 +252,8 @@ if ( supportProperty && supportProperty != propertyName ) { // we also fake the translation here var tx = transform[4] || $elem.data(translateX) || 0, ty = transform[5] || $elem.data(translateY) || 0, - origin = (value === undefined ? $.cssHooks.transformOrigin.get(elem) : value).split(' ') - ; - - // correct for keyword lengths - // TODO: the keywords are allowed to be transposed - switch (origin[0]) { - case 'left': origin[0] = '0'; break; - case 'right': origin[0] = '100%'; break; - case 'center': // no break - case undefined: origin[0] = '50%'; - } - switch (origin[1]) { - case 'top': origin[1] = '0'; break; - case 'bottom': origin[1] = '100%'; break; - case 'center': // no break - case undefined: origin[1] = '50%'; - } + origin = keywordsToPerc(value === undefined ? originPropertyGet(elem) : value).split(' ') + ; // calculate and return the correct size // find the real size of the original object @@ -268,12 +263,12 @@ if ( supportProperty && supportProperty != propertyName ) { width = $elem.outerWidth() / ratio.width, height = $elem.outerHeight() / ratio.height ; - + // turn the origin into unitless pixels // TODO: correct for non-px lengths // TODO: ems would be easy enough to handle - origin[0] = rperc.test(origin[0]) ? parseFloat(origin[0])/100*width : parseFloat(origin[0]); - origin[1] = rperc.test(origin[1]) ? parseFloat(origin[1])/100*height : parseFloat(origin[1]); + origin[0] = percToPx(elem, parseFloat(origin[0]), rperc.test(origin[0]) ? '%' : '', 0, ratio); //rperc.test(origin[0]) ? parseFloat(origin[0])/100*width : parseFloat(origin[0]); + origin[1] = percToPx(elem, parseFloat(origin[1]), rperc.test(origin[1]) ? '%' : '', 1, ratio); //rperc.test(origin[1]) ? parseFloat(origin[1])/100*height : parseFloat(origin[1]); // find the origin offset var toCenter = transformVector(transform, origin[0], origin[1]), @@ -351,8 +346,13 @@ if ( supportProperty && supportProperty != propertyName ) { if ( propertyHook ) { $.cssHooks[propertyName] = propertyHook; } +if (originPropertyHook) { + $.cssHooks[originPropertyName] = originPropertyHook; +} // we need a unique setter for the animation logic propertyGet = propertyHook && propertyHook.get || $.css; +originPropertyGet = originPropertyHook && originPropertyHook.get || $.css; +originPropertySet = originPropertyHook && originPropertyHook.set || $.css; /* * fn.animate() hooks @@ -376,8 +376,9 @@ $.fx.step.transform = function( fx ) { if ( supportMatrixFilter ) { elem.style.zoom = 1; } - + // replace "+=" in relative animations (-= is meaningless with transforms) + // TODO: this is crazy! end = end.split("+=").join(start); // parse both transform to generate interpolation list of same length @@ -427,12 +428,177 @@ $.fx.step.transform = function( fx ) { * fn.animate() hooks for transform-origin */ $.fx.step.transformOrigin = function( fx ) { - + var elem = fx.elem, + start, + end, + value = [], + pos = fx.pos, + i = 2, matches, unit = [], startVal, endVal, ratio; + + if ( !fx.state ) { + // correct for keywords + startVal = keywordsToPerc(originPropertyGet( elem, supportOriginProperty )).split(' '); + endVal = keywordsToPerc(fx.end).split(' '); + + // TODO: use a unit conversion library! + while(i--) { + // parse the start value + matches = startVal[i].match(runits); + unit[i] = matches[3]; + startVal[i] = matches[2]*1; + + // parse the end values + matches = endVal[i].match(runits); + endVal[i] = matches[2]*1; + + // units don't match and the second unit exists + if (unit[i] !== matches[3] && matches[3] && endVal[i] !== 0) { + // TODO: we've got a unit mismatch + + // start is zero and has no unit, we're all good + if ( startVal[i] === 0 && !unit[i] && matches[3] ) { + unit[i] = matches[3]; + } + + // one of the units is a percentage (hopefully the other one is a px) + else if (unit[i] === '%' || matches[3] === '%') { + if ( supportMatrixFilter ) { + ratio = transformOffset(matrix(propertyGet(elem)), 1, 1); + } + startVal[i] = percToPx(elem, startVal[i], unit[i], i, ratio); + endVal[i] = percToPx(elem, endVal[i], matches[3], i, ratio); + } + + // we're dealing with some differing non-px units + else { + // TODO: right now we don't support non-px units + } + } + } + i = 2; + + // record the doctored values on the fx object + fx.start = startVal; + fx.end = endVal; + fx.unit = unit; + } + + // read the doctored values from the fx object + start = fx.start; + end = fx.end; + unit = fx.unit; + + // animate the values + while (i--) { + value[i] = (start[i] + (end[i] - start[i]) * pos) + unit[i]; + } + value = value.join(' '); + + // set it and forget it + originPropertyHook && originPropertyHook.set ? + originPropertyHook.set( elem, value, +true ): + elem.style[supportOriginProperty] = value; } /* * Utility functions */ +function percToPx(elem, value, unit, height, useRatio) { + var ratio = 1, $elem, height, width, outer; + if (unit === '%') { + $elem = $(elem); + outer = $elem['outer' + (height ? 'Height' : 'Width')](); + + // IE doesn't report the height and width properly + if ( supportMatrixFilter ) { + ratio = (useRatio || transformOffset(matrix(propertyGet(elem)), 1, 1))[(height ? 'height' : 'width')]; + } + + // TODO: Chrome appears to use innerHeight/Width + value = outer * value / 100 / ratio; + } + return value; +} + +// keywords +function keywordsToPerc (value) { + var _top = 'top', + _right = 'right', + _bottom = 'bottom', + _center = 'center', + _left = 'left', + _space = ' ', + _0 = '0', + _50 = '50%', + _100 = '100%', + split, + i = 2; + + switch (value) { + case _top + _space + _left: // no break + case _left + _space + _top: + value = _0 + _space + _0; + break; + case _top: // no break + case _top + _space + _center: // no break + case _center + _space + _top: + value = _50 + _space + _0; + break; + case _right + _space + _top: // no break + case _top + _space + _right: + value = _100 + _space + _0; + break; + case _left: // no break + case _left + _space + _center: // no break + case _center + _space + _left: + value = _0 + _space + _50; + break; + case _right: // no break + case _right + _space + _center: // no break + case _center + _space + _right: + value = _100 + _space + _50; + break; + case _bottom + _space + _left: // no break + case _left + _space + _bottom: + value = _0 + _space + _100; + break; + case _bottom: // no break + case _bottom + _space + _center: // no break + case _center + _space + _bottom: + value = _50 + _space + _100; + break; + case _bottom + _space + _right: // no break + case _right + _space + _bottom: + value = _100 + _space + _100; + break; + case _center: // no break + case _center + _space + _center: + value = _50 + _space + _50; + break; + default: + // handle mixed keywords and other units + // TODO: this isn't 100% to spec. mixed units and keywords require the keyword in the correct position + split = value.split(_space); + if (typeof split[1] === 'undefined') { split[1] = split[0]; } + while(i--) { + switch(split[i]) { + case _left: // no break + case _top: + split[i] = _0; + break; + case _right: // no break + case _bottom: + split[i] = _100; + break; + case _center: + split[i] = _50; + } + } + value = split.join(_space); + } + return value; +} + // convert a vector function transformVector(a, x, y) { return [ @@ -452,7 +618,7 @@ function transformCorners(a, x, y) { } // measure the length of the sides -// TODO: arrays are faster than objects +// TODO: arrays are faster than objects (and compress better) function transformSides(a, x, y) { // The corners of the box var c = transformCorners(a, x, y); @@ -466,7 +632,7 @@ function transformSides(a, x, y) { } // measure the offset height and width -// TODO: arrays are faster than objects +// TODO: arrays are faster than objects (and compress better) function transformOffset(a, x, y) { // The sides of the box var s = transformSides(a, x, y); @@ -617,6 +783,7 @@ function unmatrix(matrix) { // matrix is singular and cannot be interpolated } else { + console.log(matrix); throw new Error("matrix is singular"); } From 23383f6200ac3389bf7ab8e058b2790fb157a610 Mon Sep 17 00:00:00 2001 From: heygrady Date: Thu, 15 Dec 2011 17:22:04 -0800 Subject: [PATCH 06/10] updating the readme --- README.md | 34 +++++++++++++++++++++++----------- jquery.transform2d.js | 1 - 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 8a393df..1f2c9cb 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,19 @@ returns a computed transform matrix. $(elem).css('transform') == 'matrix(0,1,-1,0,100,50)'; +Set transform-origin with a string +---------------------------------- + + $(elem).css('transform-origin', 'top left'); + $(elem).animate({transformOrigin: 'top left'}); + +Get transform-origin +-------------------- + + $(elem).css('transform-origin') + +Usually the returned units are in pixels howerver Firefox and IE less than 9 may return percentages. + Relative animations ------------------- @@ -46,20 +59,19 @@ 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 IE**, -- transformOrigin is not accessible. +- units must be px or deg (TODO) + + +Potential Translation Issues in IE 8 and Below +---------------------------------------------- -Why such restrictions with 'translate'? ---------------------------------------- +Since translate and transform-origin is unavailable in IE 8 and below, we have to emulate it using `top` and `left` properties of the element style. This library tries to emulate translate and transform-origin using position relative to reposition the element. While this closely emulates the expected behavior, if the element is already positioned, the existing position is respected as much as possible. -Since translate is unavailable in IE<9, we have to emulate it using *top* and *left* properties of the element style. -This can, of course, only work if the elements are absolutely positionned in a relatively positionned wrapper. +- absolute positioned elements are repositioned using marginTop and marginLeft to avoid conclicts +- inline styles for top and left (or marginTop and marginLeft) will be overwritten +- changes in the height or width of the element will not be dynamically reflected +- IE 7 and below incorrectly alter the offset height and width of the element to match the transformed offset height and width -Other plugins position the elements and wrap them transparently. -I think that transparently messing with the DOM often introduces unpredictible behavior. -Unpredictible behavior leads developpers to fear plugins. -*Fear leads to anger. Anger leads to hate. Hate leads to suffering.* -I prefer leaving this up to you. License ======= diff --git a/jquery.transform2d.js b/jquery.transform2d.js index de1641a..8438f83 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -783,7 +783,6 @@ function unmatrix(matrix) { // matrix is singular and cannot be interpolated } else { - console.log(matrix); throw new Error("matrix is singular"); } From be507aa4937f4726fb9969c336f6d5ca74cc6d9e Mon Sep 17 00:00:00 2001 From: heygrady Date: Fri, 16 Dec 2011 14:00:29 -0800 Subject: [PATCH 07/10] making some code more efficient and making += and -= work for transformOrigin --- README.md | 2 +- jquery.transform2d.js | 118 +++++++++++++++++++++--------------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 1f2c9cb..80d26a3 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Get transform-origin $(elem).css('transform-origin') -Usually the returned units are in pixels howerver Firefox and IE less than 9 may return percentages. +Usually the returned units are in pixels however Firefox and IE-less-than-9 may return percentages. Relative animations ------------------- diff --git a/jquery.transform2d.js b/jquery.transform2d.js index 8438f83..a38afe6 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -50,6 +50,9 @@ var div = document.createElement("div"), rAffine = /^\s*matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*(?:,\s*0(?:px)?\s*){2}\)\s*$/, runits = /^([\+\-]=)?(-?[\d+\.\-]+)([a-z]+|%)?(.*?)$/i, rperc = /%/, + _relative = 'relative', + _static = 'static', + _position = 'position', _translate = "translate", _rotate = "rotate", _scale = "scale", @@ -202,9 +205,9 @@ if ( supportProperty && supportProperty != propertyName ) { if (!origin) { // ordered backwards because we loop backwards var testProperties = [ - '-o-' + originPropertyCssName, - '-moz-' + originPropertyCssName, - '-webkit-' + originPropertyCssName, + //'-o-' + originPropertyCssName, + //'-moz-' + originPropertyCssName, + //'-webkit-' + originPropertyCssName, '-ms-' + originPropertyCssName, originPropertyCssName ], @@ -231,7 +234,7 @@ if ( supportProperty && supportProperty != propertyName ) { return origin; }, - set: function( elem, value, animate ) { + set: function( elem, value ) { var $elem = $(elem), transform = propertyGet(elem) ; @@ -266,23 +269,25 @@ if ( supportProperty && supportProperty != propertyName ) { // turn the origin into unitless pixels // TODO: correct for non-px lengths - // TODO: ems would be easy enough to handle - origin[0] = percToPx(elem, parseFloat(origin[0]), rperc.test(origin[0]) ? '%' : '', 0, ratio); //rperc.test(origin[0]) ? parseFloat(origin[0])/100*width : parseFloat(origin[0]); - origin[1] = percToPx(elem, parseFloat(origin[1]), rperc.test(origin[1]) ? '%' : '', 1, ratio); //rperc.test(origin[1]) ? parseFloat(origin[1])/100*height : parseFloat(origin[1]); + origin[0] = percToPx(elem, parseFloat(origin[0]), rperc.test(origin[0]) ? '%' : '', 0, ratio, width, height); + origin[1] = percToPx(elem, parseFloat(origin[1]), rperc.test(origin[1]) ? '%' : '', 1, ratio, width, height); // find the origin offset var toCenter = transformVector(transform, origin[0], origin[1]), fromCenter = transformVector(transform, 0, 0), offset = { - top: (fromCenter[1]) - (toCenter[1] - origin[1]), - left: (fromCenter[0]) - (toCenter[0] - origin[0]) + top: fromCenter[1] - (toCenter[1] - origin[1]), + left: fromCenter[0] - (toCenter[0] - origin[0]) }, sides = transformSides(transform, width, height) ; // apply the css - var cssPosition = $elem.css('position'), - css, + var cssPosition = $elem.css(_position), + usePosition = cssPosition === _relative || cssPosition === _static || $.transform.centerOrigin === _position, + css = {}, + propTop = usePosition ? 'top' : 'marginTop' , + propLeft = usePosition ? 'left' : 'marginLeft' , top = offset.top + ty + sides.top, left = offset.left + tx + sides.left, cssTop = 0, @@ -292,52 +297,27 @@ if ( supportProperty && supportProperty != propertyName ) { elemStyle = elem.style, currStyle = elem.currentStyle ; - - if ($.transform.centerOrigin === 'margin' || cssPosition === 'absolute' || cssPosition === 'fixed') { - // try to respect an existing marginTop/Left if it's in the CSS + + if (cssPosition === _static) { + css[_position] = _relative; + } else { + // try to respect an existing top/left if it's in the CSS // blank out the inline styles, we're going to overwrite them anyway - elemStyle.marginTop = null; - elemStyle.marginLeft = null; + elemStyle[propTop] = null; + elemStyle[propLeft] = null; // look up the CSS styles - currentTop = currStyle.marginTop; - currentLeft = currStyle.marginLeft; + currentTop = currStyle[propTop]; + currentLeft = currStyle[propLeft]; // if they're not 'auto' then use those // TODO: handle non-pixel units and percentages if (currentTop !== 'auto') { cssTop = parseInt(currentTop, 10); } if (currentLeft !== 'auto') { cssLeft = parseInt(currentLeft, 10); } - - // use margin positioning for positioned elements, the margins won't really negatively affect anything (get it? negative?) - css = { - marginTop: top + cssTop, - marginLeft: left + cssLeft - } - } else { - // try to respect an existing top/left if it's in the CSS - // look it up for position relative items - if (cssPosition !== 'static') { - // blank out the inline styles, we're going to overwrite them anyway - elemStyle.top = null; - elemStyle.left = null; - - // look up the CSS styles - currentTop = currStyle.top; - currentLeft = currStyle.left; - - // if they're not 'auto' then use those - // TODO: handle non-pixel units and percentages - if (currentTop !== 'auto') { cssTop = parseInt(currentTop, 10); } - if (currentLeft !== 'auto') { cssLeft = parseInt(currentLeft, 10); } - } - - // use relative positioning when possible; it's the most like the CSS3 spec. - css = { - position: 'relative', - top: top + cssTop, - left: left + cssLeft - } } + + css[propTop] = top + cssTop; + css[propLeft] = left + cssLeft; $elem.css(css); } }; @@ -467,6 +447,7 @@ $.fx.step.transformOrigin = function( fx ) { } startVal[i] = percToPx(elem, startVal[i], unit[i], i, ratio); endVal[i] = percToPx(elem, endVal[i], matches[3], i, ratio); + unit[i] = unit[i] === '%' ? matches[3] : unit[i]; } // we're dealing with some differing non-px units @@ -474,6 +455,11 @@ $.fx.step.transformOrigin = function( fx ) { // TODO: right now we don't support non-px units } } + + // handle +=/-= prefixes + if (matches[1]) { + endVal[i] = startVal[i] + (matches[1] === '+=' ? 1 : -1) * endVal[i] + } } i = 2; @@ -493,25 +479,24 @@ $.fx.step.transformOrigin = function( fx ) { value[i] = (start[i] + (end[i] - start[i]) * pos) + unit[i]; } value = value.join(' '); + console.log(value); // set it and forget it - originPropertyHook && originPropertyHook.set ? - originPropertyHook.set( elem, value, +true ): - elem.style[supportOriginProperty] = value; + supportMatrixFilter ? originPropertySet( elem, value ) : elem.style[supportOriginProperty] = value; } /* * Utility functions */ -function percToPx(elem, value, unit, height, useRatio) { - var ratio = 1, $elem, height, width, outer; +function percToPx(elem, value, unit, useHeight, useRatio, width, height) { + var ratio = 1, $elem, outer; if (unit === '%') { $elem = $(elem); - outer = $elem['outer' + (height ? 'Height' : 'Width')](); + outer = (useHeight ? height : width) || $elem['outer' + (useHeight ? 'Height' : 'Width')](); // IE doesn't report the height and width properly if ( supportMatrixFilter ) { - ratio = (useRatio || transformOffset(matrix(propertyGet(elem)), 1, 1))[(height ? 'height' : 'width')]; + ratio = (useRatio || transformOffset(matrix(propertyGet(elem)), 1, 1))[(useHeight ? 'height' : 'width')]; } // TODO: Chrome appears to use innerHeight/Width @@ -579,7 +564,7 @@ function keywordsToPerc (value) { // handle mixed keywords and other units // TODO: this isn't 100% to spec. mixed units and keywords require the keyword in the correct position split = value.split(_space); - if (typeof split[1] === 'undefined') { split[1] = split[0]; } + if (split[1] === undefined) { split[1] = split[0]; } while(i--) { switch(split[i]) { case _left: // no break @@ -688,8 +673,8 @@ function matrix( transform ) { val = toRadian(val); curr[0] = Math.cos(val); curr[1] = Math.sin(val); - curr[2] = -Math.sin(val); - curr[3] = Math.cos(val); + curr[2] = -curr[1]; + curr[3] = curr[0]; break; case _scale+"X": @@ -926,7 +911,22 @@ function toArray(matrix) { } $.transform = { - centerOrigin: "position" + centerOrigin: _position + toPx: function() { + + }, + + percentageToPx: function() { + + }, + + toRad: function() { + + }, + + toDeg: function() { + + } }; })( jQuery, window, document, Math ); From 63dbea56c703c60c73cf6ad12ec240798b6f0d87 Mon Sep 17 00:00:00 2001 From: heygrady Date: Fri, 16 Dec 2011 18:39:33 -0800 Subject: [PATCH 08/10] adding support full support for units to transform-origin. --- jquery.transform2d.js | 153 +++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 76 deletions(-) diff --git a/jquery.transform2d.js b/jquery.transform2d.js index a38afe6..17ce24b 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -50,6 +50,7 @@ var div = document.createElement("div"), rAffine = /^\s*matrix\(\s*1\s*,\s*0\s*,\s*0\s*,\s*1\s*(?:,\s*0(?:px)?\s*){2}\)\s*$/, runits = /^([\+\-]=)?(-?[\d+\.\-]+)([a-z]+|%)?(.*?)$/i, rperc = /%/, + _parseFloat = parseFloat, _relative = 'relative', _static = 'static', _position = 'position', @@ -256,22 +257,28 @@ if ( supportProperty && supportProperty != propertyName ) { var tx = transform[4] || $elem.data(translateX) || 0, ty = transform[5] || $elem.data(translateY) || 0, origin = keywordsToPerc(value === undefined ? originPropertyGet(elem) : value).split(' ') - ; - + ; + // calculate and return the correct size // find the real size of the original object // (IE reports the size of the transformed object) // the ratio is basically the transformed size of 1x1 object var ratio = transformOffset(transform, 1, 1), width = $elem.outerWidth() / ratio.width, - height = $elem.outerHeight() / ratio.height + height = $elem.outerHeight() / ratio.height, + i = 2, matches ; // turn the origin into unitless pixels - // TODO: correct for non-px lengths - origin[0] = percToPx(elem, parseFloat(origin[0]), rperc.test(origin[0]) ? '%' : '', 0, ratio, width, height); - origin[1] = percToPx(elem, parseFloat(origin[1]), rperc.test(origin[1]) ? '%' : '', 1, ratio, width, height); - + while (i--) { + matches = origin[i].match(runits); + if (matches[3] !== 'px') { + origin[i] = matches[3] === '%' ? percentageToPx(origin[i], elem, i, ratio, width, height) : toPx(origin[i], elem); + } else { + origin[i] = _parseFloat(origin[i]); + } + } + // find the origin offset var toCenter = transformVector(transform, origin[0], origin[1]), fromCenter = transformVector(transform, 0, 0), @@ -410,8 +417,8 @@ $.fx.step.transform = function( fx ) { $.fx.step.transformOrigin = function( fx ) { var elem = fx.elem, start, - end, value = [], + pos = fx.pos, i = 2, matches, unit = [], startVal, endVal, ratio; @@ -422,39 +429,20 @@ $.fx.step.transformOrigin = function( fx ) { // TODO: use a unit conversion library! while(i--) { - // parse the start value - matches = startVal[i].match(runits); - unit[i] = matches[3]; - startVal[i] = matches[2]*1; - - // parse the end values + // parse the end value for the +=/-= prefix matches = endVal[i].match(runits); - endVal[i] = matches[2]*1; - - // units don't match and the second unit exists - if (unit[i] !== matches[3] && matches[3] && endVal[i] !== 0) { - // TODO: we've got a unit mismatch - // start is zero and has no unit, we're all good - if ( startVal[i] === 0 && !unit[i] && matches[3] ) { - unit[i] = matches[3]; - } + // get the height/width ratio for IE + if ( supportMatrixFilter) { + ratio = transformOffset(matrix(propertyGet(elem)), 1, 1); + } - // one of the units is a percentage (hopefully the other one is a px) - else if (unit[i] === '%' || matches[3] === '%') { - if ( supportMatrixFilter ) { - ratio = transformOffset(matrix(propertyGet(elem)), 1, 1); - } - startVal[i] = percToPx(elem, startVal[i], unit[i], i, ratio); - endVal[i] = percToPx(elem, endVal[i], matches[3], i, ratio); - unit[i] = unit[i] === '%' ? matches[3] : unit[i]; - } + // convert the start value + startVal[i] = convertOriginValue(startVal[i], elem, i, ratio); + endVal[i] = convertOriginValue(endVal[i], elem, i, ratio); - // we're dealing with some differing non-px units - else { - // TODO: right now we don't support non-px units - } - } + // convert the end value + unit[i] = 'px'; // handle +=/-= prefixes if (matches[1]) { @@ -471,40 +459,32 @@ $.fx.step.transformOrigin = function( fx ) { // read the doctored values from the fx object start = fx.start; - end = fx.end; - unit = fx.unit; // animate the values while (i--) { - value[i] = (start[i] + (end[i] - start[i]) * pos) + unit[i]; + value[i] = (start[i] + (fx.end[i] - start[i]) * pos) + fx.unit[i]; } value = value.join(' '); - console.log(value); // set it and forget it supportMatrixFilter ? originPropertySet( elem, value ) : elem.style[supportOriginProperty] = value; } -/* - * Utility functions - */ -function percToPx(elem, value, unit, useHeight, useRatio, width, height) { - var ratio = 1, $elem, outer; - if (unit === '%') { - $elem = $(elem); - outer = (useHeight ? height : width) || $elem['outer' + (useHeight ? 'Height' : 'Width')](); - - // IE doesn't report the height and width properly - if ( supportMatrixFilter ) { - ratio = (useRatio || transformOffset(matrix(propertyGet(elem)), 1, 1))[(useHeight ? 'height' : 'width')]; - } - - // TODO: Chrome appears to use innerHeight/Width - value = outer * value / 100 / ratio; +function convertOriginValue(value, elem, useHeight, useRatio) { + var matches = value.match(runits); + value = matches[2] + matches[3]; + if (matches[3] !== 'px') { + value = matches[3] === '%' ? percentageToPx(value, elem, useHeight, useRatio) : toPx(value, elem); + } else { + value = _parseFloat(value); } return value; } +/* + * Utility functions + */ + // keywords function keywordsToPerc (value) { var _top = 'top', @@ -835,7 +815,7 @@ function parseFunction( type, value ) { // default value is 1 for scale, 0 otherwise defaultValue = +(!type.indexOf(_scale)), // value is parsed to radian for skew, int otherwise - valueParser = !type.indexOf(_skew) ? toRadian : parseFloat, + valueParser = !type.indexOf(_skew) ? toRadian : _parseFloat, scaleX, cat = type.replace( /[XY]/, "" ); @@ -896,11 +876,47 @@ function append( arr1, arr2, value ) { // converts an angle string in any unit to a radian Float function toRadian(value) { + var val = _parseFloat(value), PI = Math.PI; + + // TODO: why use the tilde here? seems useless? return ~value.indexOf("deg") ? - parseInt(value,10) * (Math.PI * 2 / 360): + val * (PI / 180): ~value.indexOf("grad") ? - parseInt(value,10) * (Math.PI/200): - parseFloat(value); + val * (PI / 200): + ~value.indexOf("turn") ? + val * (PI / 0.5): + val; +} + +function toPx(value, elem, prop) { + prop = prop || 'left'; + var style = elem.style[prop], + inStyle = style !== undefined && style !== null, + curr = $.css(elem, prop), // read the current value + val; + + // set the style on the target element + $.style( elem, prop, value); + val = $.css(elem, prop); + + // reset the style back to what it was + inStyle ? $.style( this, prop, curr) : elem.style[prop] = null; + return _parseFloat(val); +} + +function percentageToPx(value, elem, useHeight, useRatio, width, height) { + var ratio = 1, + $elem = $(elem), + outer = (useHeight ? height : width) || $elem['outer' + (useHeight ? 'Height' : 'Width')](); + + // IE doesn't report the height and width properly + if ( supportMatrixFilter ) { + ratio = useRatio[(useHeight ? 'height' : 'width')]; + } + + // TODO: Chrome appears to use innerHeight/Width + value = outer * _parseFloat(value) / 100 / ratio; + return value; } // Converts "matrix(A,B,C,D,X,Y)" to [A,B,C,D,X,Y] @@ -912,21 +928,6 @@ function toArray(matrix) { $.transform = { centerOrigin: _position - toPx: function() { - - }, - - percentageToPx: function() { - - }, - - toRad: function() { - - }, - - toDeg: function() { - - } }; })( jQuery, window, document, Math ); From c46300cbf2f4c0473b7fb8e7e0afad50f6ce773a Mon Sep 17 00:00:00 2001 From: heygrady Date: Fri, 16 Dec 2011 18:50:11 -0800 Subject: [PATCH 09/10] some small cleanup --- jquery.transform2d.js | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/jquery.transform2d.js b/jquery.transform2d.js index 17ce24b..10c3ae5 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -365,7 +365,7 @@ $.fx.step.transform = function( fx ) { } // replace "+=" in relative animations (-= is meaningless with transforms) - // TODO: this is crazy! + // TODO: this is not how people would expect this to work. it makes more sense to support something like: rotate(+=45deg) translate(-=10px, +=15px) end = end.split("+=").join(start); // parse both transform to generate interpolation list of same length @@ -418,9 +418,13 @@ $.fx.step.transformOrigin = function( fx ) { var elem = fx.elem, start, value = [], - pos = fx.pos, - i = 2, matches, unit = [], startVal, endVal, ratio; + i = 2, + relativeUnit, + unit = [], + startVal, + endVal, + ratio; if ( !fx.state ) { // correct for keywords @@ -430,7 +434,7 @@ $.fx.step.transformOrigin = function( fx ) { // TODO: use a unit conversion library! while(i--) { // parse the end value for the +=/-= prefix - matches = endVal[i].match(runits); + relativeUnit = endVal[i].match(runits)[1]; // get the height/width ratio for IE if ( supportMatrixFilter) { @@ -441,12 +445,9 @@ $.fx.step.transformOrigin = function( fx ) { startVal[i] = convertOriginValue(startVal[i], elem, i, ratio); endVal[i] = convertOriginValue(endVal[i], elem, i, ratio); - // convert the end value - unit[i] = 'px'; - // handle +=/-= prefixes - if (matches[1]) { - endVal[i] = startVal[i] + (matches[1] === '+=' ? 1 : -1) * endVal[i] + if (relativeUnit) { + endVal[i] = startVal[i] + (relativeUnit === '+=' ? 1 : -1) * endVal[i] } } i = 2; @@ -454,7 +455,7 @@ $.fx.step.transformOrigin = function( fx ) { // record the doctored values on the fx object fx.start = startVal; fx.end = endVal; - fx.unit = unit; + fx.unit = 'px'; } // read the doctored values from the fx object @@ -462,7 +463,7 @@ $.fx.step.transformOrigin = function( fx ) { // animate the values while (i--) { - value[i] = (start[i] + (fx.end[i] - start[i]) * pos) + fx.unit[i]; + value[i] = (start[i] + (fx.end[i] - start[i]) * pos) + fx.unit; } value = value.join(' '); @@ -470,6 +471,7 @@ $.fx.step.transformOrigin = function( fx ) { supportMatrixFilter ? originPropertySet( elem, value ) : elem.style[supportOriginProperty] = value; } +// convert a value for the origin animation, accounting for +=/-= function convertOriginValue(value, elem, useHeight, useRatio) { var matches = value.match(runits); value = matches[2] + matches[3]; @@ -878,7 +880,7 @@ function append( arr1, arr2, value ) { function toRadian(value) { var val = _parseFloat(value), PI = Math.PI; - // TODO: why use the tilde here? seems useless? + // TODO: why use the tilde here? seems useless, it's not like you'd ever want to see deg as the first character return ~value.indexOf("deg") ? val * (PI / 180): ~value.indexOf("grad") ? From 6972e5bb46785e7401714c7f8a73206673131af2 Mon Sep 17 00:00:00 2001 From: heygrady Date: Tue, 3 Jan 2012 16:08:16 -0800 Subject: [PATCH 10/10] changing to double quotes to match jQuery standard. --- jquery.transform2d.js | 96 +++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/jquery.transform2d.js b/jquery.transform2d.js index 10c3ae5..b09085b 100644 --- a/jquery.transform2d.js +++ b/jquery.transform2d.js @@ -33,8 +33,8 @@ var div = document.createElement("div"), "Webkit" + suffix, "Moz" + suffix ], - originSuffix = 'Origin', - originPropertyCssName = propertyName + '-origin', + originSuffix = "Origin", + originPropertyCssName = propertyName + "-origin", originPropertyName = propertyName + originSuffix, i = testProperties.length, supportProperty, @@ -51,9 +51,9 @@ var div = document.createElement("div"), runits = /^([\+\-]=)?(-?[\d+\.\-]+)([a-z]+|%)?(.*?)$/i, rperc = /%/, _parseFloat = parseFloat, - _relative = 'relative', - _static = 'static', - _position = 'position', + _relative = "relative", + _static = "static", + _position = "position", _translate = "translate", _rotate = "rotate", _scale = "scale", @@ -74,7 +74,7 @@ if ( !supportProperty ) { $.support.matrixFilter = supportMatrixFilter = divStyle.filter === ""; } -// px isn't the default unit of this property +// px isn"t the default unit of this property $.cssNumber[propertyName] = true; $.cssNumber[originPropertyName] = true; @@ -128,8 +128,8 @@ if ( supportProperty && supportProperty != propertyName ) { }*/ } else if ( supportMatrixFilter ) { - var translateX = suffix + '-translate-x', - translateY = suffix + '-translate-y' + var translateX = suffix + "-translate-x", + translateY = suffix + "-translate-y" ; propertyHook = { @@ -206,10 +206,10 @@ if ( supportProperty && supportProperty != propertyName ) { if (!origin) { // ordered backwards because we loop backwards var testProperties = [ - //'-o-' + originPropertyCssName, - //'-moz-' + originPropertyCssName, - //'-webkit-' + originPropertyCssName, - '-ms-' + originPropertyCssName, + //"-o-" + originPropertyCssName, + //"-moz-" + originPropertyCssName, + //"-webkit-" + originPropertyCssName, + "-ms-" + originPropertyCssName, originPropertyCssName ], i = testProperties.length, @@ -228,7 +228,7 @@ if ( supportProperty && supportProperty != propertyName ) { // otherwise use the default if (!origin) { - origin = '50% 50%'; // use percentages instead of keywords + origin = "50% 50%"; // use percentages instead of keywords $elem.data(originPropertyCssName, origin); } @@ -240,11 +240,11 @@ if ( supportProperty && supportProperty != propertyName ) { transform = propertyGet(elem) ; - // save it if there's a new value - // NOTE: undefined means we're trying to set a transform and need to handle translation + // save it if there"s a new value + // NOTE: undefined means we"re trying to set a transform and need to handle translation if (value !== undefined) { $elem.data(originPropertyCssName, value) } - // if there's no transform, don't do anything + // if there"s no transform, don"t do anything if (!transform) { return; } @@ -256,7 +256,7 @@ if ( supportProperty && supportProperty != propertyName ) { // we also fake the translation here var tx = transform[4] || $elem.data(translateX) || 0, ty = transform[5] || $elem.data(translateY) || 0, - origin = keywordsToPerc(value === undefined ? originPropertyGet(elem) : value).split(' ') + origin = keywordsToPerc(value === undefined ? originPropertyGet(elem) : value).split(" ") ; // calculate and return the correct size @@ -272,8 +272,8 @@ if ( supportProperty && supportProperty != propertyName ) { // turn the origin into unitless pixels while (i--) { matches = origin[i].match(runits); - if (matches[3] !== 'px') { - origin[i] = matches[3] === '%' ? percentageToPx(origin[i], elem, i, ratio, width, height) : toPx(origin[i], elem); + if (matches[3] !== "px") { + origin[i] = matches[3] === "%" ? percentageToPx(origin[i], elem, i, ratio, width, height) : toPx(origin[i], elem); } else { origin[i] = _parseFloat(origin[i]); } @@ -293,8 +293,8 @@ if ( supportProperty && supportProperty != propertyName ) { var cssPosition = $elem.css(_position), usePosition = cssPosition === _relative || cssPosition === _static || $.transform.centerOrigin === _position, css = {}, - propTop = usePosition ? 'top' : 'marginTop' , - propLeft = usePosition ? 'left' : 'marginLeft' , + propTop = usePosition ? "top" : "marginTop" , + propLeft = usePosition ? "left" : "marginLeft" , top = offset.top + ty + sides.top, left = offset.left + tx + sides.left, cssTop = 0, @@ -308,8 +308,8 @@ if ( supportProperty && supportProperty != propertyName ) { if (cssPosition === _static) { css[_position] = _relative; } else { - // try to respect an existing top/left if it's in the CSS - // blank out the inline styles, we're going to overwrite them anyway + // try to respect an existing top/left if it"s in the CSS + // blank out the inline styles, we"re going to overwrite them anyway elemStyle[propTop] = null; elemStyle[propLeft] = null; @@ -317,10 +317,10 @@ if ( supportProperty && supportProperty != propertyName ) { currentTop = currStyle[propTop]; currentLeft = currStyle[propLeft]; - // if they're not 'auto' then use those + // if they"re not "auto" then use those // TODO: handle non-pixel units and percentages - if (currentTop !== 'auto') { cssTop = parseInt(currentTop, 10); } - if (currentLeft !== 'auto') { cssLeft = parseInt(currentLeft, 10); } + if (currentTop !== "auto") { cssTop = parseInt(currentTop, 10); } + if (currentLeft !== "auto") { cssLeft = parseInt(currentLeft, 10); } } css[propTop] = top + cssTop; @@ -428,8 +428,8 @@ $.fx.step.transformOrigin = function( fx ) { if ( !fx.state ) { // correct for keywords - startVal = keywordsToPerc(originPropertyGet( elem, supportOriginProperty )).split(' '); - endVal = keywordsToPerc(fx.end).split(' '); + startVal = keywordsToPerc(originPropertyGet( elem, supportOriginProperty )).split(" "); + endVal = keywordsToPerc(fx.end).split(" "); // TODO: use a unit conversion library! while(i--) { @@ -447,7 +447,7 @@ $.fx.step.transformOrigin = function( fx ) { // handle +=/-= prefixes if (relativeUnit) { - endVal[i] = startVal[i] + (relativeUnit === '+=' ? 1 : -1) * endVal[i] + endVal[i] = startVal[i] + (relativeUnit === "+=" ? 1 : -1) * endVal[i] } } i = 2; @@ -455,7 +455,7 @@ $.fx.step.transformOrigin = function( fx ) { // record the doctored values on the fx object fx.start = startVal; fx.end = endVal; - fx.unit = 'px'; + fx.unit = "px"; } // read the doctored values from the fx object @@ -465,7 +465,7 @@ $.fx.step.transformOrigin = function( fx ) { while (i--) { value[i] = (start[i] + (fx.end[i] - start[i]) * pos) + fx.unit; } - value = value.join(' '); + value = value.join(" "); // set it and forget it supportMatrixFilter ? originPropertySet( elem, value ) : elem.style[supportOriginProperty] = value; @@ -475,8 +475,8 @@ $.fx.step.transformOrigin = function( fx ) { function convertOriginValue(value, elem, useHeight, useRatio) { var matches = value.match(runits); value = matches[2] + matches[3]; - if (matches[3] !== 'px') { - value = matches[3] === '%' ? percentageToPx(value, elem, useHeight, useRatio) : toPx(value, elem); + if (matches[3] !== "px") { + value = matches[3] === "%" ? percentageToPx(value, elem, useHeight, useRatio) : toPx(value, elem); } else { value = _parseFloat(value); } @@ -489,15 +489,15 @@ function convertOriginValue(value, elem, useHeight, useRatio) { // keywords function keywordsToPerc (value) { - var _top = 'top', - _right = 'right', - _bottom = 'bottom', - _center = 'center', - _left = 'left', - _space = ' ', - _0 = '0', - _50 = '50%', - _100 = '100%', + var _top = "top", + _right = "right", + _bottom = "bottom", + _center = "center", + _left = "left", + _space = " ", + _0 = "0", + _50 = "50%", + _100 = "100%", split, i = 2; @@ -544,7 +544,7 @@ function keywordsToPerc (value) { break; default: // handle mixed keywords and other units - // TODO: this isn't 100% to spec. mixed units and keywords require the keyword in the correct position + // TODO: this isn"t 100% to spec. mixed units and keywords require the keyword in the correct position split = value.split(_space); if (split[1] === undefined) { split[1] = split[0]; } while(i--) { @@ -880,7 +880,7 @@ function append( arr1, arr2, value ) { function toRadian(value) { var val = _parseFloat(value), PI = Math.PI; - // TODO: why use the tilde here? seems useless, it's not like you'd ever want to see deg as the first character + // TODO: why use the tilde here? seems useless, it"s not like you"d ever want to see deg as the first character return ~value.indexOf("deg") ? val * (PI / 180): ~value.indexOf("grad") ? @@ -891,7 +891,7 @@ function toRadian(value) { } function toPx(value, elem, prop) { - prop = prop || 'left'; + prop = prop || "left"; var style = elem.style[prop], inStyle = style !== undefined && style !== null, curr = $.css(elem, prop), // read the current value @@ -909,11 +909,11 @@ function toPx(value, elem, prop) { function percentageToPx(value, elem, useHeight, useRatio, width, height) { var ratio = 1, $elem = $(elem), - outer = (useHeight ? height : width) || $elem['outer' + (useHeight ? 'Height' : 'Width')](); + outer = (useHeight ? height : width) || $elem["outer" + (useHeight ? "Height" : "Width")](); - // IE doesn't report the height and width properly + // IE doesn"t report the height and width properly if ( supportMatrixFilter ) { - ratio = useRatio[(useHeight ? 'height' : 'width')]; + ratio = useRatio[(useHeight ? "height" : "width")]; } // TODO: Chrome appears to use innerHeight/Width