From our sponsor: Meco is a distraction-free space for reading and discovering newsletters, separate from the inbox.
Applying CSS 3D transforms to components can bring some more realism to normally flat web elements. We’ve experimented with some simple concepts for restaurant websites and came up with a 3D folded menu (a real menu, not a “web” menu). The result is a restaurant website template where the menu will open by unfolding. Clicking the linked items will reveal a modal overlay which contains some more info.
Since this “flyer” structure requires a decent amount of space, and, although we will make this responsive, we’ll add a media query for smaller screens where everything will fall back to a simplified stacked structure. The same will be applied for browsers that don’t support 3D transforms.
The thumbnails and delicious recipes are by Michael Natkin from herbivoracious.com (all the ones that have a link).
Alright, so let’s get started!
The CSS will not contain any vendor prefixes, but you will find them in the files.
The Markup
Our structure will consist of a main container with the class “rm-container” and we’ll have a wrapper inside. The wrapper will contain the three panels. Initially, we only want to see the cover of the folded menu; that is the division with the class “rm-cover”. The last panel is the one with the class “rm-right”. The middle panel is the one we see in the middle when we open the menu:
<div id="rm-container" class="rm-container"> <div class="rm-wrapper"> <div class="rm-cover"></div> <div class="rm-middle"></div> <div class="rm-right"></div> </div><!-- /rm-wrapper --> </div><!-- /rm-container -->
Inside of the rm-cover and the rm-right division, we will have a front- and a backface:
<div class="rm-front"> <div class="rm-content"> <!-- Some content --> </div><!-- /rm-content --> </div><!-- /rm-front --> <div class="rm-back"> <div class="rm-content"> <!-- Some content --> </div><!-- /rm-content --> <div class="rm-overlay"></div> </div><!-- /rm-back -->
We’ll also add an element for an overlay gradient that will give the menu parts a bit more realism. Note that the front will be empty in the last element.
The middle part of the menu will simply have another wrapper inside, no front or back part:
<div class="rm-inner"> <div class="rm-content"> <!-- Some content --> </div><!-- /rm-content --> <div class="rm-overlay"></div> </div><!-- /rm-inner -->
We will never see the back part of this part of the menu, so we won’t need that structure.
The content will consist of some text elements, like definition lists for the dishes and headlines:
<div class="rm-content"> <h4>Appetizers</h4> <dl> <dt>Bella's Artichokes</dt> <dd>Roasted artichokes with chipotle aioli and cream cheese</dd> <dt><a href="http://herbivoracious.com/2011/11/crostini-with-young-pecorino-grilled-figs-and-arugula-mint-pesto-recipe.html" class="rm-viewdetails" data-thumb="images/1.jpg">Green Love Crostini</a></dt> <dd>Crostini with young pecorino, grilled figs and arugula & mint pesto</dd> <dt>Focaccia di Carciofi</dt> <dd>Artichoke focaccia with fresh thyme</dd> <!-- ... --> </dl> <h4>Salads & More</h4> <dl> <!-- ... --> </dl> </div><!-- /rm-content -->
Notice the anchor with the class “rm-viewdetails” and the data attribute “data-thumb”. We will use this as the content for a modal overlay box that will appear when clicking on the link:
<div class="rm-modal"> <div style="background-image: url(images/1.jpg)" class="rm-thumb"></div> <h5>Green Love Crostini</h5> <p>Crostini with young pecorino, grilled figs and arugula & mint pesto</p> <a href="http://herbivoracious.com/2011/11/crostini-with-young-pecorino-grilled-figs-and-arugula-mint-pesto-recipe.html">See the recipe</a> <span class="rm-close-modal">x</span> </div>
It will contain the same headline and description that we get from the followed dd element (definition description), the thumbnail that we get from the data-attribute and the link.
And that’s the HTML, now, let’s take a look at the style.
Note that we won’t go into the styling of the text elements or inner content parts. Instead we will focus on the 3D structure, transition classes and media queries.
Tiny break: 📬 Want to stay up to date with frontend and trends in web design? Subscribe and get our Collective newsletter twice a tweek.
The CSS
We want to make the whole thing fluid, so we will give the main container a percentage width, and, since we want some 3D awesomeness, some perspective:
.rm-container { width: 33%; height: 700px; max-width: 370px; margin: 0 auto 40px auto; position: relative; perspective: 1600px; color: #2a323f; }
The wrapper and its children divisions (the cover, middle part and right part) will all have the full width and height and we’ll position them absolutely:
.rm-wrapper, .rm-wrapper > div { width: 100%; height: 100%; left: 0; top: 0; position: absolute; text-align: center; transform-style: preserve-3d; }
Since we will be working in 3D perspective, we want the transform style of these elements to be “preserve-3d”.
The cover needs a higher z-index than all the other parts and we’ll set the transform origin to be on the left edge (left center). The transition delay is for when we close the menu. The transition to the open state will contain another value, but let’s look at that a bit later:
.rm-wrapper .rm-cover { z-index: 100; transform-origin: 0% 50%; transition-delay: 0.2s; }
The middle part will have the lowest z-index of all three parts and we’ll add a subtle box shadow:
.rm-wrapper .rm-middle { z-index: 50; box-shadow: 0 4px 10px rgba(0,0,0,0.7); }
The right most part will have a z-index that is higher than the middle part one but lower than the cover one. The transform origin will be on the right edge (right center) and there will be no transition delay when we close the menu:
.rm-wrapper .rm-right { z-index: 60; transform-origin: 100% 50%; transition-delay: 0s; }
The inner divisions which are the ones with the classes “rm-front”, “rm-back” and “rm-inner” will have a paper texture as background and we’ll add a inset box shadow that will simulate a multi-line decorative border:
.rm-wrapper > div > div { background: #fff url(../images/white_paperboard.jpg); width: 100%; height: 100%; position: absolute; padding: 30px; box-shadow: inset 0 0 0 16px #fff, inset 0 0 0 17px #e6b741, inset 0 0 0 18px #fff, inset 0 0 0 19px #e6b741, inset 0 0 0 20px #fff, inset 0 0 0 21px #e6b741; }
Now, let’s add the important 3D properties. The front and the back face need backface-visibility set to hidden:
.rm-container .rm-front, .rm-container .rm-back { backface-visibility: hidden; }
Let’s rotate the backfaces so that they are behind the front parts:
.rm-container .rm-back { transform: rotateY(-180deg); }
The overlay will generally be an almost transparent gradient from light to a bit darker:
.rm-overlay { position: absolute; width: 100%; height: 100%; top: 0; left: 0; pointer-events: none; background: linear-gradient(to right, rgba(0,0,0,0) 0%, rgba(0,0,0,0.05) 100%); }
For the middle overlay we will turn the gradient to the other side:
.rm-middle .rm-overlay { background: linear-gradient(to right, rgba(0,0,0,0) 64%, rgba(0,0,0,0.05) 100%); }
Let’s add some padding the the content divisions:
.rm-content { padding: 20px; }
The modal layer will be invisible: we’ll set the opacity to 0, the pointer-event to none and we’ll translate it on the Z-axis:
.rm-modal { position: absolute; z-index: 10000; width: 120%; margin-left: -10%; top: 50%; padding: 40px; background: #fff url(../images/white_paperboard.jpg); box-shadow: inset 0 0 0 16px #fff, inset 0 0 0 17px #e6b741, inset 0 0 0 18px #fff, inset 0 0 0 19px #e6b741, inset 0 0 0 20px #fff, inset 0 0 0 21px #e6b741, 0 4px 20px rgba(0,0,0,0.4); opacity: 0; pointer-events: none; transform: translateZ(1000px); }
The idea is to reveal the modal when we click on one of the links on the menu. We’ll scale the menu down and make the modal appear from “above”. This concept is inspired by Hakim El Hattab’s modal concept Avgrund.
Let’s add some transitions and define some classes for opening the menu.
First, we’ll give a transition to the wrapper (for scaling it when opening the modal) and to the children of the wrapper, i.e. our three menu parts (for animating the “opening”/rotation):
.rm-modal { transition: transform 0.6s ease-in-out, opacity 0.6s ease-in-out; }
The idea is to add a class called “rm-open” which will trigger the menu to unfold. This class will be added with JavaScript when we click on the “View the menu” link.
When adding the class, we will define what will happen to all the elements when we open the menu.
The children of the wrapper will all get a box shadow:
.rm-container.rm-open .rm-wrapper > div { box-shadow: 0 4px 5px -3px rgba(0,0,0,0.6); }
The cover will rotate (without any delay) -180 degrees on the Y-axis. Since we defined the transform origin to be on the left center, it will open to the left:
.rm-container.rm-open .rm-cover { transform: rotateY(-180deg); transition-delay: 0s; }
The right part will rotate 180 degrees, but here we will add a transition delay of 0.2s because we want the cover to open a bit first:
.rm-container.rm-open .rm-right { transform: rotateY(180deg); transition-delay: 0.2s; }
When we click on one of the menu item that is an anchor, we will add another class called “rm-in” to the container and move the wrapper down on the Z-axis:
.rm-container.rm-in .rm-wrapper { transform: translateZ(-500px); }
The cover and the right part will be rotated a bit more to the inside:
.rm-container.rm-in .rm-cover { transform: rotateY(-150deg); } .rm-container.rm-in .rm-right { transform: rotateY(150deg); }
We need to set the transition delay to 0 seconds for this case:
.rm-container.rm-in .rm-cover, .rm-container.rm-in .rm-right, .rm-container.rm-nodelay .rm-right { transition-delay: 0s; }
The class “rm-nodelay” is an extra class that we’ll need for the right menu part when we close the modal. Remember, it had a transition delay for when we open the menu.
The modal will appear by translating it to 0px on the Z-axis and increasing the opacity to 1:
.rm-container.rm-in .rm-modal { transform: translateZ(0px); opacity: 1; pointer-events: auto; }
Let’s define some media queries for smaller screens and devices. The first media query will simply resize the wrapper:
@media screen and (max-width: 1110px) { .rm-container { height: 800px; } }
Since we made the wrapper fluid, it will need a larger height in order to contain the text.
From 960 pixels on, we no longer want to show the menu as a folded flyer but as normal, scrollable content.
For that, we’ll simply set the height of the wrapper to auto, give it 100% of width and define a maximum width:
@media screen and (max-width: 960px) { .rm-container { width: 100%; height: auto; max-width: 460px; }
All the inner divisions will no longer be absolute, but relative and we’ll give them 100% of width and set the height to auto:
.rm-wrapper, .rm-wrapper > div, .rm-wrapper > div > div { position: relative; width: 100%; height: auto; }
The content divisions will need some margin:
.rm-wrapper > div > div{ margin-bottom: 10px; box-shadow: inset 0 0 0 16px #fff, inset 0 0 0 17px #e6b741, inset 0 0 0 18px #fff, inset 0 0 0 19px #e6b741, inset 0 0 0 20px #fff, inset 0 0 0 21px #e6b741, 0 3px 5px rgba(0,0,0,0.2); }
Let’s remove the rotations:
.rm-container .rm-back, .rm-container.rm-open .rm-cover, .rm-container.rm-open .rm-right { transform: rotateY(0deg); }
We don’t need the overlays any more:
.rm-overlay, .rm-middle .rm-overlay { display: none; }
And we set the position of the modal to fixed, so that it sticks on top when we scroll:
.rm-container .rm-modal { position: fixed; width: 80%; top: 100px; left: 50%; margin: 0 0 0 -40%; transform: translateZ(0px); transition: opacity 0.6s ease-in-out 0s; }
When we click on a menu item link and the modal appears we won’t rotate anything any more:
.rm-container.rm-in .rm-cover, .rm-container.rm-in .rm-right, .rm-container.rm-in .rm-wrapper { transform: rotateY(0deg); transition-delay: 0s; } }
And that’s all the important style. For browsers that don’t support 3D transforms, we will use almost the same styling like we do for this last media query. Since we use Modernizr, we’ll just add those classes again, preceded by .no-csstransforms3d (which will be added to the body).
Now, let’s add some lines of JavaScript.
The JavaScript
We will start by caching some elements:
// main container var $container = $( '#rm-container' ), // the cover, middle and right panels $cover = $container.find( 'div.rm-cover' ), $middle = $container.find( 'div.rm-middle' ), $right = $container.find( 'div.rm-right' ), // open and close elements $open = $cover.find('a.rm-button-open'), $close = $right.find('span.rm-close'), // the links for each recipe (photo and details) $details = $container.find( 'a.rm-viewdetails' ),
The events for opening/closing the menu and also for showing each item’s details (modal) are initialized:
init = function() { initEvents(); }, initEvents = function() { $open.on( 'click', function( event ) { openMenu(); return false; } ); $close.on( 'click', function( event ) { closeMenu(); return false; } ); $details.on( 'click', function( event ) { $container.removeClass( 'rm-in' ).children( 'div.rm-modal' ).remove(); viewDetails( $( this ) ); return false; } ); },
To open/close the menu we will be adding/removing the class ‘rm-open’ from the $container. Remember, this is the class where the transitions are defined.
Note that on close, we are also removing the classes ‘rm-nodelay’ and ‘rm-in’. These are classes that are added if we click to see the menu item’s details.
openMenu = function() { $container.addClass( 'rm-open' ); }, closeMenu = function() { $container.removeClass( 'rm-open rm-nodelay rm-in' ); },
Finally, if we click to see an item’s details, a modal box will be shown with the image and the text for that item. We translate the main container on the Z-axis (and adjust the top margin in order to center it), and slightly rotate the left and right panels:
viewDetails = function( recipe ) { var title = recipe.text(), img = recipe.data( 'thumb' ), description = recipe.parent().next().text(), url = recipe.attr( 'href' ); var $modal = $( ' ' ); $modal.appendTo( $container ); var h = $modal.outerHeight( true ); $modal.css( 'margin-top', -h / 2 ); setTimeout( function() { $container.addClass( 'rm-in rm-nodelay' ); $modal.find( 'span.rm-close-modal' ).on( 'click', function() { $container.removeClass( 'rm-in' ); } ); }, 0 ); };
And that’s it! I hope you enjoyed this tutorial and find it useful!
Muy buen trabajo !
Felicitaciones !
Daniel.
WOOOOOW! Awesome, I love what I do here in Codrops, are great
Man, every restaurant site should have this… this should be required. No more garbage pdf view menus!
Totaly agree with you man!
Wow ! Impressive as always Mary Lou 🙂 Thanks a lot.
Just one question : why do you use setTimeout in the viewDetails function ?
Thanks again.
Thanks! here’s the answer to your question: http://stackoverflow.com/a/8213003/989439
This is impressive. I have to do a website for a restaurant anyway… I could include this very nice effect 🙂
Thanks for sharing that 🙂
I would pay for this! Outstanding work Mary Lou! Always thinking outside of the box..
Yes, nice & creative job !
Muy wapo, como todos los tutoriales y recursos de codrops!!
Thanks!!
Awesome work, thanks so much for sharing!
This is absolutely amazing 😀
Nice concept, looks amazing.
Only usability tip/request i have is to increase the close buttons to make it much easier to use (or just make clicking on/next to the card make the view go back). Also i don’t really understand why one would want to see the recipe. Perhaps it would be better to view ingredients (perhaps somebody doesn’t want/like certain ingredients).
Keep up the good work!
Wow… speechless!
I love this website, so cool and so inspirational. Massive respect to you guys.
Amazing!.
Simply amazing! Fantastic work, Mary Lou!
Usually I don’t post comments after seeing a good post. But I now I can’t stop me from doing it. Hats off man!! Mind blowing one. God bless you!
It’s awesome. Thanks for sharing…
Wow amazing!
Thanks for sharing…
Wow. amazaing !
What license do it?
I hope use it .
Very impressive
Amazing! You’re a genius!
Thanks for sharing
Very very nice concept.
Thanks for sharing
This is awesomeness!! Codrops you haved rocked css3 and jquery concepts 🙂
Hi,
my congratulation for the great job!
I’ve some problem to run 3d menù in IE8, suggestions?!
Thanks
I tried this using wamp and it works a treat, but ie8 isn’t working at all. I saw the caveats re browser compatibility, but are there any workarounds short of deleting Internet Explorer from my PC?? 😉
Thanks for the great tutes, i really am learning a great deal from you. So appreciative.
D
Apologies, it must be at my end as i ran your demo in ie8 without a problem. Sorry for that. Thanks again for a great tute!!
Wowowowowow! You r the reason why I wake up and switching on the PC. Thx again!
Wow. This is a really nice concept.
Though i’m experiencing a problem setting the background-image for the body to
body { background: url("../image.jpg") no-repeat scroll right bottom; }
It looks like the image’s area below the bottom edge of the the menu would be cut of.
Using firebug it seams that he body would be cut off right after the menu page(s).
Maybe someone got a solution for me. Thanks!
So far the best site (Codrops) for handy web(design) stuff. 😀
don’t know if u ran into IE10 strange behavior of the turning pages that shows the reversed front pages… any workaround?
Oh my god! Thank you so much for this awsome job. I’m really glad. Everything is clear and easy to understand even for begginers as me. Thanks again for share you knowledge. Greets from Seville, Spain.
For some reason when i upload it with all the files it doesn’t load the css…
Any ideas?
Sorry I got it now 🙂
Is it free for commercial use?
this is awesome! is there any way to use it in a wordpress page?
Is it possible to accomplish the same effect on the single div? Example: not having 3 or 4 separate div boxes but have a solid div that has text inside of it in column layout: main column has some content and right column has some content, that might have different overall height both inside a main container. So, basically, my content is not really sectionalized like you have the menu right now… If that makes any sense
Your profile is absolutely stunning.
I’m working on a very large project right now that may take up to a year of development, and I feel a bit guilty because I’ll be using quite some of your code. But honestly, your demos are full of creativity and life so it is impossible to ignore them.
For what concerns IE: I don’t care about the older versions. By the time I am finished with my project, they won’t exist anymore, and whoever still sticks to them is dumb and should not be on my website.
Is there a way to expand the awesomeness of this concept so that it’s possible to “flip” the menu (or flyer) over and read content on the back? I have been trying to execute a pamphlet idea like this for a while now! Truly fantastic.
Really great !
However, it seems to don’t work on IE10..
Do you have an idea how to fix it ? 🙂
Thank you, and really great job !
This is a great menu.
Codrops how can I delete the “See the recipe” part on the menu?
Many Thanks
It’s in the menu.js file.
This is so great! However, it’s not working in IE10 (as mentioned above). Do you know how to solve this?