Create a rotating cube with ONLY CSS
There are tons of tutorials/examples show how to do this: Example 1 Example 2 Example 3 Example 4 etc.
Pulled from Example 2:
.spinner div { position: absolute; width: 120px; height: 120px; border: 1px solid #ccc; background: rgba(255,255,255,0.8); box-shadow: inset 0 0 20px rgba(0,0,0,0.2); text-align: center; line-height: 120px; font-size: 100px;}
.spinner .face1 { -webkit-transform: translateZ(60px); -ms-transform: translateZ(60px); transform: translateZ(60px);}.spinner .face2 { -webkit-transform: rotateY(90deg) translateZ(60px); -ms-transform: rotateY(90deg) translateZ(60px); transform: rotateY(90deg) translateZ(60px); }.spinner .face3 { -webkit-transform: rotateY(90deg) rotateX(90deg) translateZ(60px); -ms-transform: rotateY(90deg) rotateX(90deg) translateZ(60px); transform: rotateY(90deg) rotateX(90deg) translateZ(60px); }.spinner .face4 { -webkit-transform: rotateY(180deg) rotateZ(90deg) translateZ(60px); -ms-transform: rotateY(180deg) rotateZ(90deg) translateZ(60px); transform: rotateY(180deg) rotateZ(90deg) translateZ(60px); }.spinner .face5 { -webkit-transform: rotateY(-90deg) rotateZ(90deg) translateZ(60px); -ms-transform: rotateY(-90deg) rotateZ(90deg) translateZ(60px); transform: rotateY(-90deg) rotateZ(90deg) translateZ(60px); }.spinner .face6 { -webkit-transform: rotateX(-90deg) translateZ(60px); -ms-transform: rotateX(-90deg) translateZ(60px); transform: rotateX(-90deg) translateZ(60px); }
.spinner { -webkit-animation: spincube 12s ease-in-out infinite; animation: spincube 12s ease-in-out infinite; -webkit-transform-style: preserve-3d; -ms-transform-style: preserve-3d; transform-style: preserve-3d; -webkit-transform-origin: 60px 60px 0; -ms-transform-origin: 60px 60px 0; transform-origin: 60px 60px 0;}
@-webkit-keyframes spincube { 16% { -webkit-transform: rotateY(-90deg); } 33% { -webkit-transform: rotateY(-90deg) rotateZ(90deg); } 50% { -webkit-transform: rotateY(180deg) rotateZ(90deg); } 66% { -webkit-transform: rotateY(90deg) rotateX(90deg); } 83% { -webkit-transform: rotateX(90deg); }}@keyframes spincube { 16% { -ms-transform: rotateY(-90deg); transform: rotateY(-90deg); } 33% { -ms-transform: rotateY(-90deg) rotateZ(90deg); transform: rotateY(-90deg) rotateZ(90deg); } 50% { -ms-transform: rotateY(180deg) rotateZ(90deg); transform: rotateY(180deg) rotateZ(90deg); } 66% { -ms-transform: rotateY(90deg) rotateX(90deg); transform: rotateY(90deg) rotateX(90deg); } 83% { -ms-transform: rotateX(90deg); transform: rotateX(90deg); }}
<div id="stage" style="width: 120px; height: 120px;"> <div class="spinner"> <div class="face1">1</div> <div class="face2">2</div> <div class="face3">3</div> <div class="face4">4</div> <div class="face5">5</div> <div class="face6">6</div> </div></div>
How to rotate a 3d cube along x axis using css?
I see for the <div>
tags you have under the cube
class, you have comments saying which are supposed to be the front side, back side, left side, etc. Simply put in classes for the names of each side and then add the following CSS for each. Then you will need to put in a keyframes
selector and animation
attribute to rotate the cube on the x-axis. My code snippet shows the full CSS followed by the full HTML:
.back { transform: translateZ(-100px) rotateY(180deg); background-color: red; opacity: 0.5;}
.right { transform: rotateY(-270deg) translateX(100px); transform-origin: top right; background-color: green; opacity: 0.5;}
.left { transform: rotateY(270deg) translateX(-100px); transform-origin: center left; background-color: yellow; opacity: 0.5;}
.top { transform: rotateX(-90deg) translateY(-100px); transform-origin: top center; background-color: purple; opacity: 0.5;}
.bottom { transform: rotateX(90deg) translateY(100px); transform-origin: bottom center; background-color: orange; opacity: 0.5;}
.front { transform: translateZ(100px); background-color: blue; opacity: 0.5;}
.wrapper { perspective: 800px; perspective-origin: 50% 100px; margin-left: 100px; margin-top: 100px;}
.cube { position: relative; width: 200px; transform-style: preserve-3d; animation: spin 5s infinite linear;}
.cube div { position: absolute; width: 200px; height: 200px; text-align: center;}
@keyframes spin { from { transform: rotateY(0); } to { transform: rotateY(360deg); }}
<div class="wrapper"> <div class="cube"> <div class="front">Front</div> <div class="back">Back</div> <div class="top">Top</div> <div class="bottom">Bottom</div> <div class="left">Left</div> <div class="right">Right</div> </div></div>
Cube rotation with css
Transition in 3d space are tricky, and different browsers can handle them differently.
Here you have your fiddle corrected.
Your best bet is to leave nothing to the browser imagination
so, instead of changing
transform: rotateY(0deg) translateZ(100px);
to
transform: rotateX(-90deg) translateZ(100px);
make the change happen from
transform: rotateX(0deg) rotateY(0deg) translateZ(100px);
to
transform: rotateX(-90deg) rotateY(0deg) translateZ(100px);
Notice that I didn't change the transform from a mathematical point of view; but now every property matches a similar one.
Note just in case you want to know, in the first case IE is making the followng transition: change the angle of rotation from 0 to -90deg. At the same time, change the axis of rotation from Y to X. So, at the middle of the transition, the rotation is wrong (from your point of view), but in a mathematic sense, both ways of understanding the transition make sense.
PURE CSS Rotating cube pause at face and then play
Please take a look here
body { color: rgb(6, 106, 117); text-transform: uppercase; font-family: sans-serif; font-size: 100%; background: #F4F6F8; padding: 3em 0 0 0; line-height: 62px; -webkit-perspective: 1000px;}
.cube { width: 30%; text-align: center; margin: 0 auto; height: 100px;
-webkit-transform-style: preserve-3d; -webkit-animation: rotate-cube 33s linear infinite;}
.front, .bottom, .top, .back { background: rgb(247, 247, 247); border: 1px solid rgba(147, 184, 189, .8); -webkit-border-radius: 5px; -webkit-box-shadow: 0 5px 20px rgba(105, 108, 109, .3), 0 0 8px 5px rgba(208, 223, 226, .4) inset; height: 100px; position: absolute; width: 100%;}
.front { -webkit-transform: translateZ(50px);}
.bottom { -webkit-transform: rotateX(90deg) translateZ(-50px) rotateY(180deg) rotateZ(180deg);}
.top { -webkit-transform: rotateX(-90deg) translateZ(-50px) rotateY(180deg) rotateZ(180deg);}
.back { -webkit-transform: rotateX(180deg) translateZ(50px);}
@-webkit-keyframes rotate-cube { 0%{-webkit-transform: rotateX(0deg);} 24%{-webkit-transform: rotateX(0deg);} 25%{-webkit-transform: rotateX(90deg);} 49%{-webkit-transform: rotateX(90deg);} 50%{-webkit-transform: rotateX(180deg);} 74%{-webkit-transform: rotateX(180deg);} 75%{-webkit-transform: rotateX(270deg);} 99%{-webkit-transform: rotateX(270deg);} 99.999%{-webkit-transform: rotateX(360deg);} 100%{-webkit-transform: rotateX(0deg);}}
<div class="cube"> <div class="front"> <h1>Front</h1> </div> <div class="bottom"> <h2>Bottom</h2> </div> <div class="top"> <h2>Top</h2> </div> <div class="back"> <h2>Back</h2> </div></div>
Rotating CSS cube on fixed axes
Note: It turns out this problem is kinda difficult to solve in CSS. If you really need a complex transformation like this where new transformations should be applied onto the previous state maybe try some other method.
Anyway, I'm first going to explain the steps I went through, the problems I faced and the steps I took to solve it. It's really convoluted and messy but it works. At the end, I've put the code I used as JavaScript.
Explanation
So I've come to understand a couple of things about transformations in CSS. One main thing is that when you pass a string of transformations to the transform
property, like this
transform: "rotateX(90deg) rotateY(90deg)"
these transformations are not combined into one single composite transformation. Instead the first one is applied, then the next one is applied on top of that and so on. So while I expected the cube to rotate diagonally by 90degrees, it didn't do that.
As @ihazkode suggested, rotate3d
was the way to go. It allows rotation around any arbitrary axes instead of being limited to X, Y and Z axes. rotate3d
takes 3 arguments
rotate3d(x, y, z, angle).
x y and z specify the rotation axis. The way to look at it is like this: Imagine drawing a line from (x,y,z)
to the transform-origin
you specified. This line will the be axis of rotation. Now imagine you are looking towards the origin from (x,y,z)
. From this view, the object will rotate clockwise by angle
degrees.
However, I still faced a problem. Although rotate3d
let's me rotate the cube in a far more intuitive way, I still faced the problem where after rotating the cube once (with the mouse) if I again clicked and tried rotating the cube, it would snap back to its original state and rotate from there, which is not what I wanted. I wanted it to rotate from it's current state, whatever rotation state that may be.
I found a very messy way to do it using the matrix3d
property. Basically I'd follow these steps every time the mousedown and mousemove events occurred
I'd calculate a vector based on the position that mousedown occured and the current mouse position from mousemove. For example, if mousedown occured at (123,145) and then a mousemove occured at (120,143), then a vector can be made from these two points as [x, y, z, m] where
x is the x component which is the new x position minus the mouse down x position = 120 - 123 = -3
y is the y component, similar to x, which = 143-145 = -2
z = 0 since the mouse cannot move in the z direction
m is the magnitude of the vector which can be calculated as squareroot(x2 + y2) = 3.606
So the mouse movement can be represented as the vector [-3, -2, 0, 3.606]
Now notice that the rotation vector of the cube should be perpendicular to the mouse movement. For example, if I move my mouse straight up by 3 pixels, the mouse movement vector is [0,-1,0,3] (y is negative because in the browser the top left corner is the origin). But if I use this vector as the rotation vector and pass it into
rotate3d
, that rotates the cube clockwise (when looking from above) around the y axis. But that's not right! If I swipe my mouse upwards, it should rotate around it's x axis! To solve this, just swap x and y and negate the new x. That is, the vector should be [1,0,0,3]. Therefore, the vector from step 1 should instead be [2,-3,0,3.606].
Now I just set the transform
property of my cube as
transform: "rotate3d(2,-3,0,3.606)"
So now, I figured out how to rotate the cube correctly based on mouse movement, without facing the previous problem of trying to make a rotateX
and then rotateY
.
- Now the cube can rotate correctly. But what if I let go of the mouse and then again perform a mousedown and try rotating the cube. If I follow the same steps from above, what happens is the new vector that I pass to
rotate3d
will replace the old one. So the cube is reset to it's initial position and the new rotation is applied to it. But that's not right. I want the cube to remain in the state it was in previously, and then from that state it should rotate further by the new rotation vector.
To do this, I need to append the new rotation onto the previous rotation. So I could do something like this
transform: "rotate3d(previous_rotation_vector) rotate3d(new_rotation_vector)"
After all, this would perform the first rotation and then perform the second rotation on top of that. But then imagine performing 100 rotations. The transform
property would need to be fed 100 rotate3d
s. That wouldn't be the best way to go about this.
Here's what I figured. At any point if you query the transform
css property of a node like
$('.cube').css('transform');
you get back one of 3 values: "none" if the object hasn't been transformed at all so far, a 2D transformation matrix (looks like matrix2d(...)
) if only 2D transformations have peen performed, or a 3D transformation matrix (looks like matrix3d(...)
otherwise.
So what I can do is, immediately after performing a rotate operation, query and get the transformation matrix of the cube and save it. Next time I perform a new rotation, do this:
transform: "matrix3d(saved_matrix_from_last_rotation) rotate3d(new_rotation_vector)"
This would first transform the cube to it's last state of rotation and then apply the new rotation on top of that. No need to pass a 100 rotate3d
s.
- There's one last problem I discovered. There's still the same issue of the axes of an object rotating along with the object.
Suppose I rotate the cube 90 degrees along the x axis with
transform: rotate3d(1,0,0,90deg);
and then rotate it from there around it's the y axis by 45 degrees with
transform: matrix3d(saved values) rotate3d(0,1,0,45deg)
I would expect the cube to rotate upwards 90 and then rotate to the right by 45. But instead it rotated up by 90 and then rotated around currently visible front face by 45 instead of rotating to the right. It's the exact same problem I mentioned in my question. The problem is, although rotate3d
allows you to rotate an object around any arbitrary axis of rotation, that arbitrary axis is still with reference to the axis of the object and not by a fixed x, y and z axes with respect to the user. It's the same gosh darn problem of the axes rotating with the object.
So if the cube is currently in some rotated state and I want it to rotate further on a vector (x,y,z) obtained through the mouse as in step 1 and 2, I first need to somehow transform this vector into it's correct position based on what state the cube is in currently.
What I noticed is if you take the rotation vector as a 4x1 matrix like this
x
y
z
angle
and took the matrix3d
matrix as a 4x4 matrix, then if I multiplied the 3D transformation matrix by the rotation vector, I get the old rotation vector but transformed into it's correct position. Now I can apply this vector after the 3d matrix as in step 3 and FINALLY the cube is behaving exactly the way it should.
JavaScript code
Okay that was enough talk. Here's the code I used. Sorry if it's not very clear.
var lastX; //stores x position from mousedown
var lastY; //y position from mousedown
var matrix3d = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] //this identity matrix performs no transformation
$(document).ready(function() {
$('body').on('mousedown', function(event) {
$('body').on('mouseup', function() {
$('body').off('mousemove');
m = $('.cube').css('transform');
//if this condition is true, transform property is either "none" in initial state or "matrix2d" which happens when the cube is at 0 rotation.
if(m.match(/matrix3d/) == null)
matrix3d = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]; //identity matrix for no transformaion
else
matrix3d = stringToMatrix(m.substring(8,m.length));
});
lastX=event.pageX;
lastY=event.pageY;
$('body').on('mousemove', function (event) {
var x = -(event.pageY - lastY);
var y = event.pageX - lastX;
var angle = Math.sqrt(x*x + y*y);
var r = [[x],[y],[0],[angle]]; //rotation vector
rotate3d = multiply(matrix3d, r); //multiply to get correctly transformed rotation vector
var str = 'matrix3d' + matrixToString(matrix3d)
+ ' rotate3d(' + rotate3d[0][0] + ', ' + rotate3d[1][0] + ', ' + rotate3d[2][0] + ', ' + rotate3d[3][0] + 'deg)';
$('.cube').css('transform',str);
});
});
});
//converts transform matrix to a string of all elements separated by commas and enclosed in parentheses.
function matrixToString(matrix) {
var s = "(";
for(i=0; i<matrix.length; i++) {
for(j=0; j<matrix[i].length; j++) {
s+=matrix[i][j];
if(i<matrix.length-1 || j<matrix[i].length-1) s+=", ";
}
}
return s+")";
}
//converts a string of transform matrix into a matrix
function stringToMatrix(s) {
array=s.substring(1,s.length-1).split(", ");
return [array.slice(0,4), array.slice(4,8), array.slice(8,12), array.slice(12,16)];
}
//matrix multiplication
function multiply(a, b) {
var aNumRows = a.length, aNumCols = a[0].length,
bNumRows = b.length, bNumCols = b[0].length,
m = new Array(aNumRows); // initialize array of rows
for (var r = 0; r < aNumRows; ++r) {
m[r] = new Array(bNumCols); // initialize the current row
for (var c = 0; c < bNumCols; ++c) {
m[r][c] = 0; // initialize the current cell
for (var i = 0; i < aNumCols; ++i) {
m[r][c] += a[r][i] * b[i][c];
}
}
}
return m;
}
Rotating CSS cube
Don't know if this is the best way to do it, but it seems adding position: absolute
, (and a width), to the sides of the cube and then changing just the translation of "front":
.front {
-webkit-transform: translateZ(-50px);
}
Seems to make it work (although texts are then displayed upside down). Didn't really checked the translate and rotate properties values themselves, so maybe setting position could be avoided, but it's what first came to my mind.
Controlling CSS cube rotation(transform) and extracting values from 3d matrix
The problem with the attempt 2 is that rotateAxisAngle does the matrix multiplication in the oposite order of what you want. And, worse still, there is no function in the class to do the multiplication in the order that you want.
As an alternate way, I have choose to use the browser itself to do the math.
I create a div that will be hidden, and where I will apply the transforms to get the new matrix.
With this approach, the javascript gets even shorter:
function applyTransform (transform) {
var cubeCalculator = $('.cubecalculator');
var cube = $('#cube');
var matrix = cubeCalculator.css('webkitTransform');
var composite = transform + ' ' + matrix;
cubeCalculator.get(0).style.webkitTransform = composite;
matrix = cubeCalculator.css('webkitTransform');
cube.get(0).style.webkitTransform = matrix;
}
// rotate using arrow keys
$(document).keyup(function(e) {
e.preventDefault();
var key = e.which,
arrow = {left: 37, up: 38, right: 39, down: 40},
t;
switch(key) {
case arrow.left:
t = 'rotateY(-90deg)';
break;
case arrow.right:
t = 'rotateY(90deg)';
break;
case arrow.up:
t = 'rotateX(90deg)';
break;
case arrow.down:
t = 'rotateX(-90deg)';
break;
}
applyTransform (t);
});
I think that the code is quite self explanatory: I apply the transform to the element as a composite of the new transform and the current transform (you don't need to extract the values from the matrix, can be applied as is)
demo
(I don't know why, it didn't work in codepen. have moved it to fiddle ...)
Finally I got the * Firefox to behave !
function applyTransform (transform1, transform2) {
var matrix, composite1, composite2;
var cubeCalculator = $('.cubecalculator');
var cube = $('#cube');
matrix = cubeCalculator.css('transform');
composite1 = transform1 + ' ' + matrix;
composite2 = transform2 + ' ' + matrix;
cubeCalculator.get(0).style.transform = composite2;
cube.get(0).style.transition = 'none';
cube.get(0).style.transform = composite1;
window.setTimeout (function() {
cube.get(0).style.transform = composite2;
cube.get(0).style.transition = 'transform 1s';
}, 10 );
}
// rotate using arrow keys
$(document).keyup(function(e) {
e.preventDefault();
var key = e.which,
arrow = {left: 37, up: 38, right: 39, down: 40},
t1, t2;
switch(key) {
case arrow.left:
t1 = 'rotateY(0deg)';
t2 = 'rotateY(-90deg)';
break;
case arrow.right:
t1 = 'rotateY(0deg)';
t2 = 'rotateY(90deg)';
break;
case arrow.up:
t1 = 'rotateX(0deg)';
t2 = 'rotateX(90deg)';
break;
case arrow.down:
t1 = 'rotateX(0deg)';
t2 = 'rotateX(-90deg)';
break;
}
applyTransform (t1, t2);
});
A little bit more complex code, but makes to the browser perfectly clear what you want it to do ...
Works fine as long as you wait till the transition is over.
HTML and CSS rotating and zoomable 3D cube
I hope this code gets the effect you wanted. I have used different approach by using a variable yAngle
to store current orientation. Here's the jsfiddle of this code.
$(document).ready(function(){ var yAngle = 0; $("#button_left").click(function(){ yAngle = yAngle-90; $("section").css("transform",'rotateY('+yAngle+'deg)'); }); $("#button_right").click(function(){ yAngle = yAngle+90; $("section").css("transform","rotateY("+yAngle+"deg)"); });});
.wrap{ perspective: 800px; perspective-origin: 50% 100px;}.cube{ margin: 0 auto; position: relative; width: 200px; transform-style: preserve-3d; transition: transform 1s;}.cube div{ box-shadow: inset 0 0 20px rgba(125,125,125,0.9); position: absolute; width: 200px; height: 200px;}.back{ background: rgba(40,40,40,0.8); transform: translateZ(-100px) rotateY(180deg);}.right{ background: rgba(189,25,400,0.3); transform: rotateY(-270deg) translateX(100px); transform-origin: top right;}.left{ background: rgba(189,25,400,0.3); transform: rotateY(270deg) translateX(-100px); transform-origin: center left;}.top{ background: rgba(189,25,400,0.3); transform: rotateX(-90deg) translateY(-100px); transform-origin: top center;}.bottom{ background: rgba(189,25,400,0.3); transform: rotateX(90deg) translateY(100px); transform-origin: bottom center;}.front{ background: rgba(189,25,400,0.3); transform: translateZ(100px);}/*@keyframes spin_left { from { transform: rotateY(0); } to { transform: rotateY(-90deg); }}.rotate-left { animation: spin_left 1s 1 linear;}@keyframes spin_right { from { transform: rotateY(0); } to { transform: rotateY(90deg); }}.rotate-right { animation: spin_right 1s 1 linear;}*/
<br><br><br><br><br><br><br><br><br><br><div class="wrap"> <section class="cube"> <div class="front">Hello</div> <div class="back"></div> <div class="top"></div> <div class="bottom"></div> <div class="left"></div> <div class="right"></div> </section></div> <input type="button" value="<--" id="button_left"><input type="button" value="-->" id="button_right"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Cube spinning on its corner
Found the answer:
According to a post on Blender the left and right angle between the horizontal and the cube in its original position is not 45deg
(as I assumed), but only 35.2644deg
.
Therefore in my CSS, I had to change the values of each occurrence of a transform: rotateX(...)
to 35.2644deg
where I used to have 45deg
and to 54.7356deg
(90 - 35.2644) where I used to have -45deg
.
Like so:
.one {
transform: rotateX(-54.7356deg) rotateY(45deg) translateZ(50px);
}
.two {
transform: rotateX(-54.7356deg) rotateY(135deg) translateZ(50px);
}
.three {
transform: rotateX(-54.7356deg) rotateY(225deg) translateZ(50px);
}
.four {
transform: rotateX(-54.7356deg) rotateY(315deg) translateZ(50px);
}
.five {
transform: rotateX(35.2644deg) rotateZ(-45deg) translateZ(50px);
}
.six {
transform: rotateX(35.2644deg) rotateY(180deg) rotateZ(-45deg) translateZ(50px);
}
I updated the JSFiddle.
Related Topics
How to Select Nth Child of Specific Tag with CSS
Command-Line Argument as Var in Sass, for Hardcoded Cdn Url's on Compile
Firefox-CSS: Border-Radius Issue for Pseudo-Element "Before"
Add All CSS Files in a Folder to Nuxt.Config
Change Height-Top of Item When Hover
Absolutely True Centred Background Image
CSS @Font-Face Not Working with Firefox
How to Animate Path Shape Without Using Smil
HTML5 Footer - Margin That I Can't Remove
How to Change My Gwt Listbox Style
Wrap Bootstrap Navbar List Items Around Centered Brand Image
How to Do a 'Float: Left' with No Wrapping
How Would You Write Media Queries for Multiple Screen Sizes