Is there a way to get the angle [through JavaScript] by which the element is rotated?
Seems like a reasonable request. So we have some HTML:
<div id="i-am-rotated">text</div>
And it’s rotated through CSS:
#i-am-rotated {
-webkit-transform: rotate(30deg);
-moz-transform: rotate(30deg);
-ms-transform: rotate(30deg);
-o-transform: rotate(30deg);
}
Our goal is to get the number “30” from that element via JavaScript. The modern way to access styling information from an element is getComputedStyle()
(Supported in all modern browsers and IE 9+, older IE supported currentStyle()
). Let’s try and get it with getComputedStyle():
var el = document.getElementById("i-am-rotated");
var st = window.getComputedStyle(el, null);
var tr = st.getPropertyValue("-webkit-transform") ||
st.getPropertyValue("-moz-transform") ||
st.getPropertyValue("-ms-transform") ||
st.getPropertyValue("-o-transform") ||
st.getPropertyValue("transform") ||
"Either no transform set, or browser doesn't do getComputedStyle";
You might think the value returned would be “rotate(30deg)” and we could run parseInt()
on it and get “30”. But unfortunately that doesn’t work. The actual value we get back is this:
console.log(tr);
// matrix(0.8660254037844387, 0.49999999999999994, -0.49999999999999994, 0.8660254037844387, 0, 0)
The browser turns the CSS rotation transform into a matrix transform. I imagine it does this to simplify what could be multiple transforms on the single element into one value. So what are we to do?
Nicolas Gallager researched the matrix transformation for rotate transforms. Which is essentially this:
rotate(Xdeg) = matrix(cos(X), sin(X), -sin(X), cos(X), 0, 0);
We really just need one of these to make a quick equation. We need to get the arcsin (inverse of sin, sin-1) of the values, making sure to get it in radians.
First we get our hands on the separate individual matrix values:
// UPDATE: below was causing errors sometimes...
// var values = tr.split('(')[1].split(')')[0].split(',');
// Replace with... (thanks Thierry)
var values = tr.split('(')[1],
values = values.split(')')[0],
values = values.split(',');
var a = values[0]; // 0.866025
var b = values[1]; // 0.5
var c = values[2]; // -0.5
var d = values[3]; // 0.866025
Then we know sin(X) == 0.5
so asin(0.5) == radians
and degrees == radians * 180/π
.
So:
var angle = Math.round(Math.asin(b) * (180/Math.PI));
console.log(angle);
// 30
Yay!
Nicolas took it a bit further by accounting for scale as well. With the full code below, the rotation value can be extracted with any number of other transforms applied.
#complex-transform {
-webkit-transform: rotate(30deg) scale(1.2) skew(10deg) translate(5px, 5px);
-moz-transform: rotate(30deg) scale(1.2) skew(10deg) translate(5px, 5px);
-ms-transform: rotate(30deg) scale(1.2) skew(10deg) translate(5px, 5px);
-o-transform: rotate(30deg) scale(1.2) skew(10deg) translate(5px, 5px);
}
var el = document.getElementById("complex-transform");
var st = window.getComputedStyle(el, null);
var tr = st.getPropertyValue("-webkit-transform") ||
st.getPropertyValue("-moz-transform") ||
st.getPropertyValue("-ms-transform") ||
st.getPropertyValue("-o-transform") ||
st.getPropertyValue("transform") ||
"fail...";
// With rotate(30deg)...
// matrix(0.866025, 0.5, -0.5, 0.866025, 0px, 0px)
console.log('Matrix: ' + tr);
// rotation matrix - http://en.wikipedia.org/wiki/Rotation_matrix
var values = tr.split('(')[1];
values = values.split(')')[0];
values = values.split(',');
var a = values[0];
var b = values[1];
var c = values[2];
var d = values[3];
var scale = Math.sqrt(a*a + b*b);
// arc sin, convert from radians to degrees, round
// DO NOT USE: see update below
var sin = b/scale;
var angle = Math.round(Math.asin(sin) * (180/Math.PI));
// works!
console.log('Rotate: ' + angle + 'deg');
UPDATE: Turns out this line is way more reliable for calculating angle:
var angle = Math.round(Math.atan2(b, a) * (180/Math.PI));
So good!
scale() and rotation() ok! but…
What about skew() and translate()?
The `skew()` (X or Y) transform will still affect the 2×2 matrix that is used to represent the rotation, so to account for skew you need to do further calculations. Christian Schaefer came across this in the spec: http://www.w3.org/TR/2011/WD-css3-2d-transforms-20111215/#matrix-decomposition
I’ll probably never use this, but it was interesting to see that the computer uses a matrix to calculate the rotation, and not radians or degrees or something.
Unfortunately, doing arcsin on one of the values is not enough, because it leaves us with ambiguity.
I’ll skip the boring part about inverting non-injective periodic functions (this comment box is too small); just see that
so asin is not enough to distinguish between the two.
You’ll need also information from the cosine: if it’s positive no worries, if it’s negative you have to take 180°-arcsin(b)
File this under “I’ll never need this in real life” ;)
I think that there is something missing from the calculations.
I modified the fiddle to put a 130 degree rotation on (an then moved it some more so you could see it) and the script reported a 50 degree rotation:
http://jsfiddle.net/459jS/98/
Great catch!
Looks like factoring in the new math that Nicolas linked to above, we can change the arc sign stuff to this:
and it’s returning good results for all angles I’ve tried, including the problem Agos presented above.
Hey, anyone checking this out might be interested in Easy Transform. It’s nowhere near as powerful or useful as it could be. Fork it and add to it if you can, or just general feedback and usage would be great:
Easy Transform @ Github
Ahh, I remember doing similar things in Java3D – Converting the matrix back into a rotation angle.
Is it the same in Firefox and Opera?
awesome
Math is so much fun :)
Great post, Chris, and also great analysis, Nicolas.
Wow, that seriously needs to be a jQuery method.
I don’t know how long I have been waiting for this, but it has been a while.
I may be some time.
Thank you
Great article! Thanks for the advice, greatly appreciated!
hai, this might be just me, but why can’t we just use:
or something similar, in my chrome console, it returns:
amy I missing something here ?
I was having issues with this code when I needed to get a rotation value above 180 degrees.
Turns out the
atan2
function only returns a value from -pi to pi, so you only get degree values from -180 to 180. For example, when I tried to get a rotation of 270deg the code was returning -90.The only change is to add a check to see if the result in radians is less than zero, and if so, add
(2 * pi)
to the resultSee my pen here for a comparison: http://codepen.io/jjeaton/pen/bzolH
thank you !! :D
I think the examples are slightly wrong (not for the main article content but for the scale stuff) plus there is a bit of a random code going on!
Then, as far as I can tell (its really rarely / mathematically documented) matrices work like the following:
So this next bit gets really confusing (the last example):
I would write it as:
For a start, it stops us just mapping the values[0], values[1], values[2] etc to the equally unhelpful a, b, c but importantly shows us that the
var scale = Math.sqrt(a*a + b*b);
line can simply be removed, as we already have scale, in each dimension. Also, if we use that example then the scale = sqrt((0.866025 * 0.866025) + (0.5 * 0.5)) =0.9999996503
But in fact, we know the matrix itself says the scale is 0.8660254037844387 on the x, and is 0.8660254037844387 on the y, so I’m guessing that number should read 0.8660254037844387. (That equation used finds the hypotenuse of a triangle using incorrect points unless I’m being stupid!)
Just starting to learn about these so I hope I’m correct, very embarrassed if not. Feel free to delete this comment if you incorporate into the article : )
Gracias!! me salvaste de horas de romperme la cabeza!!
Thank you! saved me hours of head-breaking!