Creating a 3D Rotating Menu with CSS3

July 23, 2012 at 3:35 pm By
Download Demo

Today we’re going to be making this pretty cool rotating box effect menu. It will degrade in browsers which do not support 3D transforms to just a regular menu and will therefore work in the latest stable releases of all major browsers and the developers release of chrome.

3D Transforms

To accomplish this effect we need to create a 3D object. For that reason, each list item will have an anchor, in which is contained 4 spans. Each span represents the sides of of a cuboid. We don’t really need the two extra sides that make up the cuboid since we’re only going to be looking at it from the front. We’ll position each span in the right position. I’ve also included some sub menus by adding extra list elements within each li tag.

<ul class="menu">
	<li class="home current">
		<a href="http://www.html5canvastutorials.com/blog/">
			<span>Home &nbsp;<i class="ss-navigatedown">&nbsp;</i></span>
			<span>Home &nbsp;<i class="ss-navigatedown">&nbsp;</i></span>
			<span>Home &nbsp;<i class="ss-navigatedown">&nbsp;</i></span>
			<span>Home &nbsp;<i class="ss-navigatedown">&nbsp;</i></span>
		</a>
		<ul>
			<li><a href="">Sub Menu</a></li>
			<li><a href="">Sub Menu</a></li>
		</ul>
	</li>
	<li class="about">
		<a href="http://www.html5canvastutorials.com/blog/">
			<span>About</span>
			<span>About</span>
			<span>About</span>
			<span>About</span>
		</a>
	</li>
	<li class="contact">
		<a href="http://www.html5canvastutorials.com/blog/">
			<span>Contact &nbsp;<i class="ss-navigatedown">&nbsp;</i></span>
			<span>Contact &nbsp;<i class="ss-navigatedown">&nbsp;</i></span>
			<span>Contact &nbsp;<i class="ss-navigatedown">&nbsp;</i></span>
			<span>Contact &nbsp;<i class="ss-navigatedown">&nbsp;</i></span>
		</a>
		<ul>
			<li><a href="">Sub Menu</a></li>
			<li><a href="">Sub Menu</a></li>
		</ul>
	</li>
	<li class="twitter">
		<a href="http://www.html5canvastutorials.com/blog/">
			<span>Twitter</span>
			<span>Twitter</span>
			<span>Twitter</span>
			<span>Twitter</span>
		</a>
	</li>
</ul>

I’m going to be using the Symbolset icon font to create the down arrows (That’s what the <i> tags are). It’s pretty cheap, and I’d highly recommend it, however there are other ways to accomplish down arrows and this is not necessary. If you’re not using symbolset then just remove the <i> tags.

CSS Time!

3D Transforms are only fully supported by webkit browsers which according to statcounter make up about 39% of the market. Firefox supports 3D transforms but the implementation is slightly different, and therefore it doesn’t really look as good as it does in webkit browsers. To tackle this lack of support we’re using 4 spans. The first span will be the default state, the second will be the hover or current state, the third will be the hover state for the current link, and the 4th will be used for browsers with no 3D support.

We’re going to move the spans around until we form a cube, which in 3D space will look a little like this:
box-diagram

We begin by setting the perspective and some other CSS properties for the main menu tag.

.menu {
	-webkit-perspective: 100000;
	-moz-perspective: 100000;
	-o-perspective: 100000;
	-ms-perspective: 100000;
	perspective: 1200;
	display: block;
	width: 800px;	
	height: 65px;
	margin: 0 auto;
	list-style: none;
	padding: 0;
	padding: 0 0 0 30px;
	border-radius: 5px;
	background-color: #546065;
}

Next we have to set the li elements to preserve 3D mode so they can be manipulated as 3D objects. We also need to set transitions so that everything will run smoothly on hover. The rest is just regular old CSS.

.menu li {
	-webkit-transform-style: preserve-3d;
	-moz-transform-style: preserve-3d;
	-o-transform-style: preserve-3d;
	-ms-transform-style: preserve-3d;
	transform-style: preserve-3d;
	height: 21px;
	width: 140px;
	margin: 0 10px 0 0;
	float: left;
	position: relative;
	-webkit-transition: all 0.2s linear;
	-moz-transition: all 0.2s linear;
	-o-transition: all 0.2s linear;
	-ms-transition: all 0.2s linear;
	transition: all 0.2s linear;
}

.menu li a {
	display: block;
	color: #fff;
	font-weight: bold;
	box-sizing: border-box;
	height: inherit;
	width: inherit;
	font-size: 1.2em;
	text-decoration: none;
	text-transform: uppercase;
	font-family: Arial, sans-serif;
}

.menu li a span {
	height: inherit;
	width: inherit;
	padding: 22px 0;
	text-align: center;
	position: absolute;
	left: 0;
	display: block;
}

Next is the difficult part, positioning each span in the correct place. Much of this was trial and error, and of course the values will change depending on what size you wish the menu elements to be. I’ve also added the styles for the ‘current’ menu item. Instead of changing the color, I just rotated the object so that the background was a different color!


.menu li a span:first-of-type {
	-webkit-transform: translateZ(74px);
	-moz-transform: translateZ(74px);
	-o-transform: translateZ(74px);
	-ms-transform: translateZ(74px);
	transform: translateZ(74px);
	background-color: #3e4649;
	box-shadow: inset 0px 0px 15px rgba(0,0,0,0.1);
}
	
.menu li a span:nth-of-type(2) {
	background: #fa623f;
	box-shadow: inset 0px 35px 30px -30px rgba(255,255,255,0.1);
	-webkit-transform: rotate3d(1,0,0,90deg) translateZ(32px) translateY(42px);
	-moz-transform: rotate3d(1,0,0,90deg) translateZ(32px) translateY(42px);
	-o-transform: rotate3d(1,0,0,90deg) translateZ(32px) translateY(42px);
	-ms-transform: rotate3d(1,0,0,90deg) translateZ(32px) translateY(42px);
	transform: rotate3d(1,0,0,90deg) translateZ(32px) translateY(42px);
}

.menu li a span:nth-of-type(3) {
	background: #f8876d;
	box-shadow: inset 0px 35px 30px -30px rgba(255,255,255,0.1);
	-webkit-transform: rotate3d(1,0,0,180deg) translateZ(-9px);
	-moz-transform: rotate3d(1,0,0,180deg) translateZ(-9px);
	-o-transform: rotate3d(1,0,0,180deg) translateZ(-9px);
	-ms-transform: rotate3d(1,0,0,180deg) translateZ(-9px);
	transform: rotate3d(1,0,0,180deg) translateZ(-9px);
}

.menu li a span:nth-of-type(4) {
	background: #3e4649;
	-webkit-transform: rotate3d(1,0,0,270deg) translateZ(33px) translateY(-42px);
	-moz-transform: rotate3d(1,0,0,270deg) translateZ(33px) translateY(-42px);
	-o-transform: rotate3d(1,0,0,270deg) translateZ(33px) translateY(-42px);
	-ms-transform: rotate3d(1,0,0,270deg) translateZ(33px) translateY(-42px);
	transform: rotate3d(1,0,0,270deg) translateZ(33px) translateY(-42px);
}

.current {
	-webkit-transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 	
	-moz-transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 	
	-o-transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 	
	-ms-transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 	
	transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 	
}

Next we have to set what happens on hover. When the user hovers over the li elements we want them to rotate. We’ll also have to do a little repositioning to ensure everything goes in the right place. We also need to set what happens when the user hovers over the 4th span (it will just act like a normal menu).

.menu > li:hover {
	-webkit-transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 
	-moz-transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 
	-o-transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 
	-ms-transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 
	transform: rotate3d(1,0,0,-90deg) translateZ(-20px); 
}

.menu > .current:hover {
	-webkit-transform: rotate3d(1,0,0,-180deg) translateY(-44px);
	-moz-transform: rotate3d(1,0,0,90deg) translateZ(-64px);
	-o-transform: rotate3d(1,0,0,-180deg) translateY(-44px);
	-ms-transform: rotate3d(1,0,0,-180deg) translateY(-44px);
	transform: rotate3d(1,0,0,-180deg) translateY(-44px);
}

/* This is for browsers without 3D support. They will just see a normal menu */
.menu > .current span:nth-of-type(4), .menu > li:hover span:nth-of-type(4) {
	background: #fa623f;
}

.menu > .current:hover span:nth-of-type(4) {
	background: #f8876d;
}

The final step is to make the drop down menus work. These are optional and the menu will function better without them, however I thought it’d be good to show you exactly how to implement them. Since the initial rotating animation lasts 0.3 seconds, we want to delay the drop downs from appearing for 0.3 seconds to ensure everything runs smoothly. Things get a little complicated with 3D transforms, and since we cant use displays with transitions, we need to set opacity of the drop downs to 0. This has the unfortunate side effect of showing when the user hovers below the menu. We can’t really prevent this in browsers without 3D support, but in most cases it won’t be a huge deal. To tackle it in browsers with 3D support, we just rotate the sub menus so they become unclickable.

After the user hovers over the main li element, the sub menus rotate back into position and fade in. I also had to do a quick resize of a menu element so everything fitted correctly.

.menu ul {
	opacity: 0;
	width: 100%;
	border-radius: 0;
	padding: 0;
}

.menu > li:hover ul {
	opacity: 1;
	height: auto;
	background: #fa623f;
	-webkit-transition: opacity 0.1s linear 0.3s;
	-moz-transition: opacity 0.1s linear 0.3s;
	-o-transition: opacity 0.1s linear 0.3s;
	-ms-transition: opacity 0.1s linear 0.3s;
	transition: opacity 0.1s linear 0.3s;
	-webkit-transform: rotate3d(1,0,0, 90deg) translateY(114px);
	-moz-transform: rotate3d(1,0,0, 90deg) translateY(115px);
	-o-transform: rotate3d(1,0,0, 90deg) translateY(114px);
	-ms-transform: rotate3d(1,0,0, 90deg) translateY(114px);
	transform: rotate3d(1,0,0, 90deg) translateY(114px);
	position: absolute;
	left: 0;
	top: 65px;
}

.menu > li ul {
	-webkit-transform: rotate3d(1,0,0, 90deg);	
	-moz-transform: rotate3d(1,0,0, 90deg);	
	-o-transform: rotate3d(1,0,0, 90deg);	
	-ms-transform: rotate3d(1,0,0, 90deg);	
	transform: rotate3d(1,0,0, 90deg);	
}
.menu > .current:hover ul {
	-webkit-transform: rotate3d(1,0,0, 180deg) translateY(145px);	
	-moz-transform: rotate3d(1,0,0, 270deg) translateY(31px);	
	-o-transform: rotate3d(1,0,0, 180deg) translateY(141px);	
	-ms-transform: rotate3d(1,0,0, 180deg) translateY(161px);	
	transform: rotate3d(1,0,0, 180deg) translateY(161px);	
	top: 65px;
	background: #f8876d;
}

.menu ul li {
	display: block;
	float: none;
	border-radius: 0;
	background: inherit;
	width: 170px;
	height: 40px;
}

.menu ul li a {
	box-sizing: border-box;
	padding: 8px 0 0 25px;
	background: inherit;
	width: inherit;
}

.menu ul li a:hover {
	background-color: #eb350b;
}

.menu ul .current a:hover {
	background-color: #ce5f45;
}
.menu .contact {
	width: 170px;
}

And we’re done! As a quick note, I had to implement the firefox declarations slightly differently because firefox (for some reason) didn’t implement the specification in the same way as webkit. I assumed webkit was implementing it correctly since they are the main driving force behind the 3D transform specification. However, should Firefox change its implementation in the future (likely), you would have to change the firefox transforms to suit. I hope you’ve enjoyed this tutorial! Check out the demo or download below.

While we’re talking about browser compatibility, I would like to bring up the issue of Google Chrome. It seems the latest version of webkit has altered the 3D transforms, so this demo will not work in regular Google Chrome. It will however work in the developers build. This is down to the fact that the developers build has the latest version of webkit. So the drop downs won’t really work in regular chrome. We can probably expect in a week or two for this demo to work perfectly with chrome when they release a new version.