3d Navbar That Rotates

2.1k views Asked by At

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.

2

There are 2 answers

2
Armin On BEST ANSWER

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/

5
Josh Burgess On

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.