How to combine rotateX(50deg) rotateY(20deg) rotateZ(15deg)
in shorthand rotate3d()
?
rotateX(50deg)
is equivalent to rotate3d(1, 0, 0, 50deg)
rotateY(20deg)
is equivalent to rotate3d(0, 1, 0, 20deg)
rotateZ(15deg)
is equivalent to rotate3d(0, 0, 1, 15deg)
So...
rotateX(50deg) rotateY(20deg) rotateZ(15deg)
is equivalent to
rotate3d(1, 0, 0, 50deg) rotate3d(0, 1, 0, 20deg) rotate3d(0, 0, 1, 15deg)
For a generic rotate3d(x, y, z, α)
, you have the matrix
where
You now get the matrices for each of the 3 rotate3d
transforms and you multiply them. And the resulting matrix is the matrix corresponding to the resulting single rotate3d
. Not sure how to easy it is to extract the values for rotate3d
out of it, but it's sure easy to extract those for a single matrix3d
.
In the first case (rotateX(50deg)
or rotate3d(1, 0, 0, 50deg)
), you have:
x = 1
, y = 0
, z = 0
, α = 50deg
So the first row of the matrix in this case is 1 0 0 0
.
The second one is 0 cos(50deg) -sin(50deg) 0
.
The third one 0 sin(50deg) cos(50deg) 0
.
And the fourth one is obviously 0 0 0 1
.
In the second case, you have x = 0
, y = 1
, z = 0
, α = 20deg
.
First row: cos(20deg) 0 sin(20deg) 0
.
Second row: 0 1 0 0
.
Third row: -sin(20) 0 cos(20deg) 0
.
Fourth: 0 0 0 1
In the third case, you have x = 0
, y = 0
, z = 1
, α = 15deg
.
First row: cos(15deg) -sin(15deg) 0 0
.
Second row sin(15deg) cos(15deg) 0 0
.
And the third and the fourth row are 0 0 1 0
and 0 0 0 1
respectively.
Note: you may have noticed that the signs of the sin values for the rotateY transform are different than for the other two transforms. It's not a computation mistake. The reason for this is that, for the screen, you have the y-axis pointing down, not up.
So these are the three 4x4
matrices that you need to multiply in order to get the 4x4
matrix for the resulting single rotate3d
transform. As I've said, I'm not sure how easy it can be to get the 4 values out, but the 16 elements in the 4x4 matrix are exactly the 16 parameters of the matrix3d
equivalent of the chained transform.
EDIT:
Actually, it turns out it's pretty easy... You compute the trace (sum of diagonal elements) of the matrix for the rotate3d
matrix.
4 - 2*2*(1 - cos(α))/2 = 4 - 2*(1 - cos(α)) = 2 + 2*cos(α)
You then compute the trace for the product of the three 4x4
matrices, you equate the result with 2 + 2*cos(α)
you extract α
. Then you compute x
, y
, z
.
In this particular case, if I computed correctly, the trace of the matrix resulting from the product of the three 4x4
matrices is going to be:
T =
cos(20deg)*cos(15deg) +
cos(50deg)*cos(15deg) - sin(50deg)*sin(20deg)*cos(15deg) +
cos(50deg)*cos(20deg) +
1
So cos(α) = (T - 2)/2 = T/2 - 1
, which means that α = acos(T/2 - 1)
.
Syntax:
rotate3d(x, y, z, a)
Values:
x
Is a <number>
describing the x-coordinate of the vector denoting the axis of rotation.y
Is a <number>
describing the y-coordinate of the vector denoting the axis of rotation.z
Is a <number>
describing the z-coordinate of the vector denoting the axis of rotation.a
Is an <angle>
representing the angle of the rotation. A positive angle denotes a clockwise rotation, a negative angle a counter-clockwise one.Like in :
.will-distort{
transform:rotate3d(10, 10, 10, 45deg);
}
Fiddled here
Caniuse it here
More docs about it
Depends on what you are trying to do, this 'hack' could help you. Let's say you are doing animation, and you want add transformation after transformation and so on, and you don't want the CSS to look like it's doing 100's of transformations:
This works in chrome: 1. Apply whatever transform you want to an element. 2. Next time you want to add a transform, add it to the computed transform: "window.getComputedStyle(element).transform" - but make sure to put the new transform to the left. 3. Now your transform would look like "rotateZ(30deg) matrix3d(......). 4. Next time you want to add another transform, repeat the process - Chrome always reduces the transforms to matrix3d notation.
TL;DR- apply whatever transforms you want, and then get the computed matrix3d transformation.
This trick also lets you quickly (that is, without doing any math by yourself) make a functionality that rotates an object with respect to your reference frame in any direction. See the sample below:
EDIT: I have added xyz translations as well. Using this, it would be very easy to place objects in specific 3d locations with specific orientations in mind. Or...imagine a cube that bounces and changes it's spin axis with each bounce depending on how it lands!
var boxContainer = document.querySelector('.translator'),
cube = document.getElementById('cube'),
optionsContainer = document.getElementById('options');
var dims = ['x', 'y', 'z'];
var currentTransform;
var currentTranslate;
var init = function () {
optionsContainer.querySelector('.xRotation input')
.addEventListener('input', function (event) {
if (currentTransform != 'none') {
var newTransform = 'rotateX(' + (360 - event.target.value) + 'deg) ' + currentTransform;
} else {
var newTransform = 'rotateX(' + (360 - event.target.value) + 'deg)';
}
cube.style.transform = newTransform;
}, false);
optionsContainer.querySelector('.yRotation input')
.addEventListener('input', function (event) {
if (currentTransform != 'none') {
var newTransform = 'rotateY(' + (360 - event.target.value) + 'deg) ' + currentTransform;
} else {
var newTransform = 'rotateY(' + (360 - event.target.value) + 'deg)';
}
cube.style.transform = newTransform;
}, false);
optionsContainer.querySelector('.zRotation input')
.addEventListener('input', function (event) {
if (currentTransform != 'none') {
var newTransform = 'rotateZ(' + (360 - event.target.value) + 'deg) ' + currentTransform;
} else {
var newTransform = 'rotateZ(' + (360 - event.target.value) + 'deg)';
}
cube.style.transform = newTransform;
}, false);
optionsContainer.querySelector('.xTranslation input')
.addEventListener('input', function (event) {
if (currentTranslate != 'none') {
var newTransform = 'translateX(' + (100 - event.target.value) + 'px) ' + currentTranslate;
} else {
var newTransform = 'translateX(' + (100 - event.target.value) + 'px)';
}
boxContainer.style.transform = newTransform;
}, false);
optionsContainer.querySelector('.yTranslation input')
.addEventListener('input', function (event) {
if (currentTranslate != 'none') {
var newTransform = 'translateY(' + (100 - event.target.value) + 'px) ' + currentTranslate;
} else {
var newTransform = 'translateY(' + (100 - event.target.value) + 'px)';
}
boxContainer.style.transform = newTransform;
}, false);
optionsContainer.querySelector('.zTranslation input')
.addEventListener('input', function (event) {
if (currentTranslate != 'none') {
var newTransform = 'translateZ(' + (500 - event.target.value) + 'px) ' + currentTranslate;
} else {
var newTransform = 'translateZ(' + (500 - event.target.value) + 'px)';
}
boxContainer.style.transform = newTransform;
}, false);
reset();
};
function reset() {
currentTransform = window.getComputedStyle(cube).transform;
currentTranslate = window.getComputedStyle(boxContainer).transform;
optionsContainer.querySelector('.xRotation input').value = 360;
optionsContainer.querySelector('.yRotation input').value = 360;
optionsContainer.querySelector('.zRotation input').value = 360;
optionsContainer.querySelector('.xTranslation input').value = 100;
optionsContainer.querySelector('.yTranslation input').value = 100;
optionsContainer.querySelector('.zTranslation input').value = 500;
}
window.addEventListener('DOMContentLoaded', init, false);
document.addEventListener('mouseup', reset, false);
.translator
{
height: 200px;
position: absolute;
width: 200px;
transform-style: preserve-3d;
}
.threeSpace
{
height: 200px;
moz-perspective: 1200px;
o-perspective: 1200px;
perspective: 200px;
position: absolute;
transform-origin: 50px 50px 100px;
webkit-perspective: 1200px;
width: 100px;
perspective-origin: 100px 25px;
transform-style: preserve-3d;
}
#pointer{
position:relative;
height:2px;
width:2px;
top:25px;
left:100px;
background:blue;
z-index:9999;
}
#cube
{
height: 100%;
moz-transform-origin: 90px 110px 0px;
moz-transform-style: preserve-3d;
o-transform-origin: 90px 110px 0px;
o-transform-style: preserve-3d;
position: absolute;
transform-origin: 90px 110px 0px;
transform-style: preserve-3d;
webkit-transform-origin: 90px 110px 0px;
webkit-transform-style: preserve-3d;
width: 100%;
}
#cube .midPoint{
position:absolute;
top:48px;
left:48px;
height:1px;
width:1px;
background:green;
}
#cube figure
{
border: 2px solid black;
color: white;
display: block;
font-size: 60px;
font-weight: bold;
height: 96px;
line-height: 96px;
position: absolute;
text-align: center;
width: 96px;
/* transform-style: preserve-3d; */
}
#cube .front
{
background: hsl(0, 100%, 50%);
}
#cube .back
{
background: hsl(60, 100%, 50%);
}
#cube .right
{
background: hsl(120, 100%, 50%);
}
#cube .left
{
background: hsl(180, 100%, 50%);
}
#cube .top
{
background: hsl(240, 100%, 50%);
}
#cube .bottom
{
background: hsl(300, 100%, 50%);
}
#cube .front
{
moz-transform: translateZ(50px);
o-transform: translateZ(50px);
transform: translateZ(50px);
webkit-transform: translateZ(50px);
}
#cube .back
{
moz-transform: rotateX(-180deg) translateZ(50px);
o-transform: rotateX(-180deg) translateZ(50px);
transform: rotateX(-180deg) translateZ(50px);
webkit-transform: rotateX(-180deg) translateZ(50px);
}
#cube .right
{
moz-transform: rotateY(90deg) translateZ(50px);
o-transform: rotateY(90deg) translateZ(50px);
transform: rotateY(90deg) translateZ(50px);
webkit-transform: rotateY(90deg) translateZ(50px);
}
#cube .left
{
moz-transform: rotateY(-90deg) translateZ(50px);
o-transform: rotateY(-90deg) translateZ(50px);
transform: rotateY(-90deg) translateZ(50px);
webkit-transform: rotateY(-90deg) translateZ(50px);
}
#cube .top
{
moz-transform: rotateX(90deg) translateZ(50px);
o-transform: rotateX(90deg) translateZ(50px);
transform: rotateX(90deg) translateZ(50px);
webkit-transform: rotateX(90deg) translateZ(50px);
}
#cube .bottom
{
moz-transform: rotateX(-90deg) translateZ(50px);
o-transform: rotateX(-90deg) translateZ(50px);
transform: rotateX(-90deg) translateZ(50px);
webkit-transform: rotateX(-90deg) translateZ(50px);
}
#options{
position:absolute;
width:80%;
top:40%;
}
#options input
{
width: 60%;
}
<body>
<div class="threeSpace">
<div id="pointer"></div>
<div class="translator">
<div id="cube">
<figure class="front"><div class='midPoint'></div></figure>
<figure class="back"></figure>
<figure class="right"></figure>
<figure class="left"></figure>
<figure class="top"></figure>
<figure class="bottom"></figure>
</div>
</div>
</div>
<section id="options">
<p class="xRotation">
<label>xRotation</label>
<input type="range" min="0" max="720" value="360" data-units="deg" />
</p>
<p class="yRotation">
<label>yRotation</label>
<input type="range" min="0" max="720" value="360" data-units="deg" />
</p>
<p class="zRotation">
<label>zRotation</label>
<input type="range" min="0" max="720" value="360" data-units="deg" />
</p>
<p class="xTranslation">
<label>xTranslation</label>
<input type="range" min="0" max="200" value="100" data-units="deg" />
</p>
<p class="yTranslation">
<label>yTranslation</label>
<input type="range" min="0" max="200" value="100" data-units="deg" />
</p>
<p class="zTranslation">
<label>zTranslation</label>
<input type="range" min="0" max="1000" value="500" data-units="deg" />
</p>
</section>
</body>
The exact value is rotate3d(133,32,58,58deg)
See the fiddle (For chrome and Safari, using -webkit-transform)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With