I'm trying to create a 3d navbar using pure CSS with transforms, transitions and perspective.
Here is my code:
.navbar-fixed-bottom {
background: transparent;
}
.navbar-perspective {
width: 100%;
height: 100%;
position: relative;
-webkit-perspective: 1100px;
-moz-perspective: 1100px;
perspective: 1100px;
-webkit-perspective-origin: 50% 0;
-moz-perspective-origin: 50% 0;
perspective-origin: 50% 0;
}
.navbar-perspective > div {
margin: 0 auto;
position: relative;
text-align: justify;
-webkit-backface-visibility: hidden;
-moz-backface-visibility: hidden;
backface-visibility: hidden;
-webkit-transition: all 0.5s;
-moz-transition: all 0.5s;
transition: all 0.5s;
height: 50px;
font-size:20px;
}
.navbar-primary {
background-color: #cccccc;
z-index: 2;
-webkit-transform-origin: 0% 100%;
-moz-transform-origin: 0% 100%;
transform-origin: 0% 100%;
}
.navbar .navbar-secondary,
.navbar .navbar-tertiary {
background-color: #bfbfbf;
width: 100%;
-webkit-transform-origin: 0% 0%;
-moz-transform-origin: 0% 0%;
transform-origin: 0% 0%;
z-index: 1;
-webkit-transform: rotateX(-90deg);
-moz-transform: rotateX(-90deg);
transform: rotateX(-90deg);
-webkit-transition: top 0.5s;
-moz-transition: top 0.5s;
transition: top 0.5s;
position: absolute;
top: 0;
}
.navbar .navbar-tertiary {
background-color: #b3b3b3;
}
.navbar-rotate-primary {
height: 50px;
}
.navbar-rotate-primary .navbar-primary {
-webkit-transform: translateY(0%) rotateX(0deg);
-moz-transform: translateY(0%) rotateX(0deg);
transform: translateY(0%) rotateX(0deg);
}
.navbar-rotate-primary .navbar-secondary,
.navbar-rotate-primary .navbar-tertiary {
top: 100%;
-webkit-transition: -webkit-transform 0.5s;
-moz-transition: -moz-transform 0.5s;
transition: transform 0.5s;
-webkit-transform: rotateX(-90deg);
-moz-transform: rotateX(-90deg);
transform: rotateX(-90deg);
}
.navbar-rotate-secondary,
.navbar-rotate-tertiary {
height: 50px;
}
.navbar-rotate-secondary .navbar-primary,
.navbar-rotate-tertiary .navbar-primary {
-webkit-transform: translateY(-100%) rotateX(90deg);
-moz-transform: translateY(-100%) rotateX(90deg);
transform: translateY(-100%) rotateX(90deg);
}
.navbar-rotate-secondary .navbar-secondary,
.navbar-rotate-tertiary .navbar-secondary {
top: 100%;
-webkit-transition: -webkit-transform 0.5s;
-moz-transition: -moz-transform 0.5s;
transition: transform 0.5s;
-webkit-transform: rotateX(0deg) translateY(-100%);
-moz-transform: rotateX(0deg) translateY(-100%);
transform: rotateX(0deg) translateY(-100%);
}
.navbar-rotate-secondary-fallback .navbar-primary,
.navbar-rotate-tertiary-fallback .navbar-primary {
display: none;
}
.navbar-rotate-tertiary .navbar-secondary {
-webkit-transform: translateY(-100%) rotateX(90deg);
-moz-transform: translateY(-100%) rotateX(90deg);
transform: translateY(-100%) rotateX(90deg);
}
.navbar-rotate-tertiary .navbar-tertiary {
top: 100%;
-webkit-transition: -webkit-transform 0.5s;
-moz-transition: -moz-transform 0.5s;
transition: transform 0.5s;
-webkit-transform: rotateX(0deg) translateY(-100%);
-moz-transform: rotateX(0deg) translateY(-100%);
transform: rotateX(0deg) translateY(-100%);
}
<html>
<head>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
</head>
<body>
<nav id="navigation-bottom" class="navbar navbar-fixed-bottom">
<div class="navbar-perspective">
<div class="navbar-primary">
<a href="javascript:void(0);" onclick="$('#navigation-bottom').attr('class','navbar navbar-fixed-bottom navbar-rotate-secondary')">Rotate To Face 2</a>
</div>
<div class="navbar-secondary">
<a href="javascript:void(0);" onclick="$('#navigation-bottom').attr('class','navbar navbar-fixed-bottom navbar-rotate-tertiary')">Rotate To Face 3</a>
</div>
<div class="navbar-tertiary">
<a href="javascript:void(0);" onclick="$('#navigation-bottom').attr('class','navbar navbar-fixed-bottom navbar-rotate-primary')">Rotate Back To Face 1</a>
</div>
</div>
</nav>
</body>
</html>
I've got the first two faces to rotate properly using a 3d effect, but the third face does not look right. You will notice as you rotate from second to third that the top does not rotate correctly and looks flat.
Any help is greatly appreciated.
Fiddle with a flipping box
This is vastly different from where you started, but let me post my CSS and show you the fiddle, and then I'll edit in a longer explanation of how and why this works:
HTML
<section class="container">
<nav id="nav-box" class="show-front">
<div class="front">
<a href="#">Show Bottom</a>
</div>
<div class="bottom">
<a href="#">Show Back</a></div>
<div class="back">
<a href="#">Show Top</a></div>
<div class="top">
<a href="#">Show Front</a></div>
</nav>
</section>
CSS
.container {
position: relative;
perspective: 1000px;
transform: scale(0.95);
}
#nav-box {
width: 100%;
height: 50px;
position: absolute;
transform-origin: center center;
transform-style: preserve-3d;
transition: transform 0.5s;
}
#nav-box div {
width: 100%;
height: 50px;
display: block;
position: absolute;
transition: background-color 0.5s;
}
#nav-box .front { transform: rotateX( 0deg ) translateZ( 25px ); background-color: #ccc; }
#nav-box .back { transform: rotateX( 180deg ) translateZ( 25px ); background-color: #ccc; }
#nav-box .top { transform: rotateX( 90deg ) translateZ( 25px ); background-color: #ccc; }
#nav-box .bottom { transform: rotateX( -90deg ) translateZ( 25px ); background-color: #ccc; }
#nav-box.show-front { transform: rotateY( 0deg ); }
#nav-box.show-front .bottom { background-color: #a0a0a0; }
#nav-box.show-front .top { background-color: #e0e0e0; }
#nav-box.show-back { transform: rotateX( -180deg ); }
#nav-box.show-back .bottom { background-color: #e0e0e0; }
#nav-box.show-back .top { background-color: #a0a0a0; }
#nav-box.show-top { transform: rotateX( -90deg ); }
#nav-box.show-top .front { background-color: #a0a0a0; }
#nav-box.show-top .back { background-color: #e0e0e0; }
#nav-box.show-bottom { transform: rotateX( 90deg ); }
#nav-box.show-bottom .front { background-color: #e0e0e0; }
#nav-box.show-bottom .back { background-color: #a0a0a0; }
Explanation of the HTML/CSS
Setting up our box
You started thinking about this the wrong way, I hate to say. You approached this as "How can I treat these four sides like a box" rather than "How can I make a box in CSS?"
So let's learn how to make a box.
First, we establish a box
container. Since this is a navigation box, let's call it nav-box
. All the transforms we apply (save for the shading, which we'll get to later) will be done on our nav-box
.
The rules on our nav-box
will determine how it behaves as an object. Let's discuss two in particular: transform-origin
and transform-style
transform-origin
defaults to center center, but I wanted to call it out here. This is basically going to tell our box: Hey, we need you to pivot around your absolute center. If we set this up as transform-origin: center bottom' it would look like the box is spinning around its bottom edge.
center top` and it would spin around its top edge. I don't think that's what you want, though.
transform-style
needs to be set to preserve-3d
. What this does is instruct the browser to not fuss with the elements with transform
underneath it. Other options include flat
which tells the browser to ignore rotates underneath it. The reason we want to set preserve-3d
on our nav-box
here is to ensure the transforms
we applied to the box sides are preserved when we transform
the parent. Neat stuff, huh?
Setting up our sides
We're setting our sides as children of our nav-box
and just positioning them in the order that they should be in using rotateX
:
0
rotation for the front
180deg
for the back
-90deg
for the bottom
90deg
for the top
We could also set a left and right side right now with .left { transform: rotateY(-90deg); } .right { rotateY(90deg); }
. Note that we used the Y
axis for those two examples.
Secondly, we set a translateZ
value of 25px
. So what the hell is this doing? It's telling our boxes they need to move 25px
from the center of the parent relative to their respective rotations. Why did we choose 25px
? Because it's exactly half the height of each of our boxes. This means that it will flush up nicely with the sides at either edge.
And then the fun part:
We shade the boxes based on their position and what is facing the screen. The background colors are relative to what side of the box we're showing with show-front
, show-back
, etc. The side on the bottom gets darker, the side on the top gets lighter. I just liked that – totally not necessary to accomplish this task but makes it look a little more realistic.
Hope that helps!
Update for IE
Fiddle Example
So, there's not much pretty about this once we get through fixing it up for IE, but here it is. All preserve-3d
is doing is applying the transforms for you when we rotate a container, instead of flattening them. If we can't use preserve-3d
, we have to calculate based on the amount of total rotation.
This solution does that. I won't go as in-depth on this one, rather than to highlight how much more JavaScript this requires, and to highlight the .rewind
class:
#nav-box.rewind div {
backface-visibility: hidden;
}
Because we have to manually rewind this solution, we'll have to prevent the z-index reordering to be applied at the wrong times. That's where backface-visibility
comes in.
Example showing depth in IE
Another example without the need for the rewind
class
Hope that solves IE for you.
First of all, thank you to all that commented and answered to this question, especially Josh!
Josh, your example works perfectly for browsers that support preserve-3d. The update you posted without preserve-3d appears flat on IE so it was still not perfected for all browsers.
After three days of headaches, I realized the problem. The origin of the sides was not being set correctly. The sides need to rotate around a point that is half way in on the Z axis.
Once I've updated the origin to :
transform-origin: 25px 25px -25px;
Once this was correct, all you really need to do is update the rotation of the object. No need to use any transformation of the X,Y,Z coordinates.
Here's the fiddle and the solution for a 3D Navigation bar that rotates and works for all browsers including IE10+.
http://jsfiddle.net/tx0emcxe/