Fun With Transform Origins and Matrices
Forcing IE to support a transform origin, transform origin IE. Plus a transform animation class for YUI 2.7.
Contents...
- Implementing Transform Origin In IE + Matrix Math
- Examples + Source Code
- Code Sample
- YUI Matrix Animation Class
- Limitations with IE
- Other Findings
- References, Inspiration
Implementing Transform Origin In IE
The key to this is IE solution is that as the matrix filter is applied to the element, its offsetWidth and offsetHeight are the dimensions of the transformed element's bounding box if and only if the sizing method parameter is set to 'auto expand'.
.matrix-transform{
filter: progid:DXImageTransform.Microsoft.Matrix(M11='0.7071',M12='-0.2479',M21='0.7071',M22='1.1663', sizingMethod='auto expand');
}
If 'auto expand' is not used, then you will have to write code that finds the dimensions of the bounding box of the transformed element and manually set the width and height yourself. If you use 'auto expand' then IE will resize the element for you free of charge.
Another issue is that in IE the element needs to be positioned absolutely so the element will be removed from the document flow. As a workaround I wrap the element in a relatively positioned element sized to the same dimensions.
So with this in mind to support a transform origin in internet explorer the following steps must be taken: Note: for this example we'll be trying to achieve the following styling on an element:
div.example { transform: rotate(45deg) skewX(33deg); transform-origin(150% 33%); width:100px; height: 100px; }
The element's bounding box will be a transparent blue and has the dimensions of width:97px, height:187px
The transformed element will have a containing bounding box's left and top properties set in order to illustrate each step.
A rotation of 45 degrees, and skewing about the x axis 33 degrees will result in the following matrix (I've truncated to 4 decimal places):
0.7071 | -0.2479 |
0.7071 | 1.1663 |
Let's proceed with the implementation steps.
-
Always have the original dimensions of the element handy.
width:100px; height: 100px;
-
Take the original center of the element and translate it by the negated transform origin.
center = (50, 50) origin = (100 * 150%, 100 * 33%) = (150, 33) translated center = (50 - 150, 50 - 33) = (-100, 17)
-
Apply the matrix transform to the result.
[[0.7071, -0.2479], [0.7071, 1.1663]] * [-100, 17] = [0.7071 * -100 + -0.2479 * 17, 0.7071 * -100 + 1.1663 * 17] = [-74.9242, -50.8834]
-
Translate the result by the transform origin.
[-74.9242 + 150, -50.8834 + 33] = [75.0758, -17.8834]
-
Subtract from the x value of the result half the width of the bounding box, and from the y value of the result half the height of the bounding box.
[75.0758 - 97/2, -17.8834 - 187/2] ~= [26.5, -112]
-
This is where the top left corner of the bounding box should be
Actual Code:
#true-transform { -moz-transform: rotate(45deg) skewX(33deg); -moz-transform-origin: 150% 33%; -webkit-transform: rotate(45deg) skewX(33deg); -webkit-transform-origin: 150% 33%;
Mathematically, this is nothing new, it's really just the algorithm for transforming a point or set of points (think corners of a polygon) about an origin which is:
- Translate by the negated value of the origin.
- Apply the transform to the points, i.e the matrix.
- Translate by the origin.
The tough part is the origin dimensions, I don't have a viewport change listener or anything going on, so if the screen is resized in anyway then the effects won't be consistent in ie since the code has sized the element but all in all I think it's working pretty well.
Examples
Here are some rudimentary examples:
- Origin Manipulation through Drag 'n Drop
- Tabview POC using matrix animation
-
Download the Code
Just Updated 1/07/2011. Could not transform properly in IE because my code was shortsighted and couldn't parse floating point numbers. Never fear, this POC code works now :)
Just updated 03/10/2010. Could not rotate by increments of 90 degrees due to css style object's choking on really small numbers in exponential format, i.e 6.2345197345e-17.
Thanks to Denny Wong for finding the issue.
Coding
To apply transform styles element by element I created a utility object named ElementTransformer that adds in some nice fluentness for readability. For convenience I found typing new ElementTransformer to be a pain so using a function caled elTransform makes things a bit more wrist friendly.
This will enable you to write code like:
elTransform('id-of-element'). origin('left top'). matrix({rotate:60/*degrees*/});
All methods that require an angle are normalized to degrees. You're welcome to modify the code as you see fit, personally I find working with bulky floating point numbers due to radians a pain in the wrist.
Another thing is all code (minus the MatrixAnim class) is library agnostic. You're free to drop it in and muddle with it as you see fit. Using a solid library to handle some of the plumbing will probably stablize the code even more.
Animation Class
I also wrote an animation class utilizing YUI 2.7.0.
The code is along the same lines as the coding conventions used there like:
var anim = new MatrixAnim('some-element', { rotate: { by: 360 }, scale :{by:0.25}, skewY : {from:10, to:45} }, 3, YAHOO.util.Easing.bounceOut); //kick off the animation anim.animate ();
Limitations with IE
- The element must be rendered in order for the transform origin to work properly. Note: It does not need to be visible though.
-
Only works with block level elements like div, p, table, h1-6.
Trying to transform a table row or table cell element or a span nested within an anchor tag will result in failure.
Why?
Because IE requires some work arounds. My solution entails wrapping the transformed element with a relatively positioned div sized to the same dimensions. This wrapper element allows:
- Layout preservation.
- transform origin support because the absolutely positioned element will now be positioned relative to its parent.
- In ie 8, absolutely positioned elements inside of the transformed element are not transformed. They are in version ie 6 and 7 though.
Other Findings
I found an issue with Mozilla's documentation on the matrix method in css.
From the documentation at https://developer.mozilla.org/en/CSS/-moz-transform#matrix on 7/22/2009
-moz-transform: matrix(a, b, c, d, tx, ty)
/* Where a, b, c, d build the transformation matrix
a | b |
c | d |
and tx, ty are the translate values.*/
The problem is evident in that the resultant matrix of a 45 degree rotation rounded to 4 decimal places is:
0.7071 | -0.7071 |
0.7071 | 0.7071 |
If we had an element with the dimensions of 100x100 pixels it would have the following coordinates:
[[0,0], [100,0], [100,100], [0,100]]
Applying a 45 degree matrix to each point (transform origin at 0,0) yields:
[[0,0], [70.71, 70.71], [0, 141.2], [-70.71, 70.71]]
Applying this matrix in firefox 3.5 or webkit browsers it will rotate the element counter clockwise.
-moz-transform: rotate(45deg); -webkit-transform: rotate(45deg);
45 degree rotation
-(moz|webkit)-transform: matrix(0.7071, -0.7071, 0.7071, 0.7071, 0, 0);
With an equivalent 45 degree matrix, But I'm going the wrong way.
This is an easy fix
Given a matrix
a | b |
c | d |
You merely apply it by columns, not rows and this will give the correct result.
-(moz|webkit)-transform: matrix (a, c, b, d, dx, dy)
-moz-transform: rotate(45deg); -webkit-transform: rotate(45deg);
Been rotated 45 degrees
-(moz|webkit)-transform: matrix(0.7071, 0.7071, -0.7071, 0.7071, 0, 0);
Some other nuances between firefox and safari. If you apply the transform to an element and query the element's position, for instance this element above Firefox will consistenly return 101 x 101.
Webkit's results are very inconsistent, I wasn't able to track down a pattern or any rhyme or reason to it. In my origin manipulation example I had to get the region of the animated element outside of my drag drop end handler. I found if I got the region of the element in the handler while the element was being animated the element would shoot all over the place.
Last Modified 9/18/2009 4:33 PM PST
References, Inspiration
I was really inspired to do this after seeing these uses of IE's CSS filter property to support matrix transformations.
-
Bringing CSS Transforms to IE
The coverflow effect achieved here is really impressive.
-
http://blogs.microsoft.co.il/blogs/nir_tayeb/archive/2008/10/29/fx-matrix.aspx
Great animation, I ended up writing a matrix animation class for YUI 2.7.
-
Birdmanizer
Totally cool, no IE support though, but there is work being done for a jQuery patch: http://www.zachstronaut.com/posts/2009/02/22/jquery-patch-css-transform.html
- MSDN Matrix Filter Documentation
-
A Great JavaScript Docmentation Resource