From our sponsor: Ready to show your plugin skills? Enter the Penpot Plugins Contest (Nov 15-Dec 15) to win cash prizes!
This Blueprint is page navigation effect based on the Dribbble shot Stacked navigation by Ilya Kostin. The idea is to show a navigation when clicking on the menu button and transform all pages in 3D and move them to the bottom of the viewport. The next two pages are shown in the back of the current page as a stack. When clicking on a menu item, the respective page comes up.
Please note that we are using a couple of modern CSS properties, so only contemporary browsers are supported.
Browser Support:- ChromeSupported
- FirefoxSupported
- Internet ExplorerSupported from version 10+
- SafariSupported
- OperaSupported
The HTML
<!-- navigation --> <nav class="pages-nav"> <div class="pages-nav__item"><a class="link link--page" href="#page-home">Home</a></div> <div class="pages-nav__item"><a class="link link--page" href="#page-docu">Documentation</a></div> <div class="pages-nav__item"><a class="link link--page" href="#page-manuals">Manuals</a></div> <div class="pages-nav__item"><a class="link link--page" href="#page-software">Software</a></div> <div class="pages-nav__item"><a class="link link--page" href="#page-custom">Customization & Settings</a></div> <div class="pages-nav__item"><a class="link link--page" href="#page-training">Training</a></div> <div class="pages-nav__item pages-nav__item--small"><a class="link link--page link--faded" href="#page-buy">Where to buy</a></div> <div class="pages-nav__item pages-nav__item--small"><a class="link link--page link--faded" href="#page-blog">Blog & News</a></div> <div class="pages-nav__item pages-nav__item--small"><a class="link link--page link--faded" href="#page-contact">Contact</a></div> <div class="pages-nav__item pages-nav__item--social"> <a class="link link--social link--faded" href="#"><i class="fa fa-twitter"></i><span class="text-hidden">Twitter</span></a> <a class="link link--social link--faded" href="#"><i class="fa fa-linkedin"></i><span class="text-hidden">LinkedIn</span></a> <a class="link link--social link--faded" href="#"><i class="fa fa-facebook"></i><span class="text-hidden">Facebook</span></a> <a class="link link--social link--faded" href="#"><i class="fa fa-youtube-play"></i><span class="text-hidden">YouTube</span></a> </div> </nav> <!-- /navigation--> <!-- pages stack --> <div class="pages-stack"> <!-- page --> <div class="page" id="page-home"> <!-- page content --> </div> <!-- /page --> <div class="page" id="page-docu"> <!-- page content --> </div> <div class="page" id="page-manuals"> <!-- page content --> </div> <div class="page" id="page-software"> <!-- page content --> </div> <div class="page" id="page-custom"> <!-- page content --> </div> <div class="page" id="page-training"> <!-- page content --> </div> <div class="page" id="page-buy"> <!-- page content --> </div> <div class="page" id="page-blog"> <!-- page content --> </div> <div class="page" id="page-contact"> <!-- page content --> </div> </div> <!-- /pages-stack --> <button class="menu-button"><span>Menu</span></button> <script src="js/classie.js"></script> <script src="js/main.js"></script>
The CSS
html.js, .js body { overflow: hidden; height: 100vh; } /* Pages nav */ .pages-nav { display: -webkit-flex; display: flex; -webkit-flex-wrap: wrap; flex-wrap: wrap; -webkit-justify-content: center; justify-content: center; -webkit-align-items: center; align-items: center; padding: 20px; text-align: center; background: #0e0f0f; } .js .pages-nav { position: absolute; top: 0; left: 0; width: 100%; height: 50vh; padding: 30px; pointer-events: none; opacity: 0; background: transparent; -webkit-transition: -webkit-transform 1.2s, opacity 1.2s; transition: transform 1.2s, opacity 1.2s; -webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1); transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1); -webkit-transform: translate3d(0, 150px, 0); transform: translate3d(0, 150px, 0); } .js .pages-nav--open { pointer-events: auto; opacity: 1; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } .pages-nav__item { width: 33%; padding: 1em; } .js .pages-nav__item { padding: 0 10%; } .pages-nav .pages-nav__item--social { width: 100%; opacity: 0; -webkit-transition: -webkit-transform 1.2s, opacity 1.2s; transition: transform 1.2s, opacity 1.2s; -webkit-transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1); transition-timing-function: cubic-bezier(0.2, 1, 0.3, 1); -webkit-transform: translate3d(0, 20px, 0); transform: translate3d(0, 20px, 0); } .pages-nav--open .pages-nav__item--social { opacity: 1; -webkit-transition-delay: 0.35s; transition-delay: 0.35s; -webkit-transform: translate3d(0, 0, 0); transform: translate3d(0, 0, 0); } .link { font-size: 0.85em; font-weight: bold; position: relative; letter-spacing: 1px; text-transform: uppercase; } .link:hover, .link:focus { color: #fff; } .link--page { display: block; color: #cecece; } .link--page:not(.link--faded)::before { content: ''; position: absolute; top: 100%; left: 50%; width: 30px; height: 2px; margin: 5px 0 0 -15px; background: #fff; -webkit-transition: -webkit-transform 0.3s; transition: transform 0.3s; -webkit-transform: scale3d(0, 1, 1); transform: scale3d(0, 1, 1); } .link--page:hover:before { -webkit-transform: scale3d(1, 1, 1); transform: scale3d(1, 1, 1); } .link--faded { color: #4f4f64; } .link--faded:hover, .link--faded:focus { color: #5c5edc; } .link--page.link--faded { font-size: 0.65em; } .link--social { font-size: 1.5em; margin: 0 0.75em; } .text-hidden { position: absolute; display: block; overflow: hidden; width: 0; height: 0; color: transparent; } /* Pages stack */ .js .pages-stack { z-index: 100; pointer-events: none; -webkit-perspective: 1200px; perspective: 1200px; -webkit-perspective-origin: 50% -50%; perspective-origin: 50% -50%; } .js .page { position: relative; z-index: 5; overflow: hidden; width: 100%; height: 100vh; pointer-events: auto; background: #2a2b30; box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1); } .js .pages-stack--open .page { cursor: pointer; -webkit-transition: -webkit-transform 0.45s, opacity 0.45s; transition: transform 0.45s, opacity 0.45s; -webkit-transition-timing-function: cubic-bezier(0.6, 0, 0.4, 1); transition-timing-function: cubic-bezier(0.6, 0, 0.4, 1); } .js .page--inactive { position: absolute; z-index: 0; top: 0; opacity: 0; } /* page content */ .info { font-size: 1.25em; max-width: 50%; margin-top: 1.5em; } .poster { position: absolute; bottom: 4vh; left: 60%; max-width: 100%; max-height: 80%; } /* Menu button */ .menu-button { position: absolute; z-index: 1000; top: 30px; left: 30px; width: 30px; height: 24px; padding: 0; cursor: pointer; border: none; outline: none; background: transparent; } .no-js .menu-button { display: none; } .menu-button::before, .menu-button::after, .menu-button span { background: #5f656f; } .menu-button::before, .menu-button::after { content: ''; position: absolute; top: 50%; left: 0; width: 100%; height: 2px; pointer-events: none; -webkit-transition: -webkit-transform 0.25s; transition: transform 0.25s; -webkit-transform-origin: 50% 50%; transform-origin: 50% 50%; } .menu-button span { position: absolute; left: 0; overflow: hidden; width: 100%; height: 2px; text-indent: 200%; -webkit-transition: opacity 0.25s; transition: opacity 0.25s; } .menu-button::before { -webkit-transform: translate3d(0, -10px, 0) scale3d(0.8, 1, 1); transform: translate3d(0, -10px, 0) scale3d(0.8, 1, 1); } .menu-button::after { -webkit-transform: translate3d(0, 10px, 0) scale3d(0.8, 1, 1); transform: translate3d(0, 10px, 0) scale3d(0.8, 1, 1); } .menu-button--open span { opacity: 0; } .menu-button--open::before { -webkit-transform: rotate3d(0, 0, 1, 45deg); transform: rotate3d(0, 0, 1, 45deg); } .menu-button--open::after { -webkit-transform: rotate3d(0, 0, 1, -45deg); transform: rotate3d(0, 0, 1, -45deg); } @media screen and (max-width: 60em) { .info { max-width: 100%; } .poster { position: relative; top: auto; left: auto; display: block; max-width: 100%; max-height: 50vh; margin: 0 0 0 50%; } .pages-nav__item { width: 50%; min-height: 20px; } .link--page { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; } .link--social { margin: 0 0.1em; } } @media screen and (max-width: 40em) { .js .pages-nav { display: block; padding: 10px 20px 0 20px; text-align: left; } .js .pages-nav__item { width: 100%; padding: 4px 0; } .js .pages-nav__item--small { display: inline-block; width: auto; margin-right: 5px; } .pages-nav__item--social { font-size: 0.9em; } .menu-button { top: 15px; right: 10px; left: auto; } .info { font-size: 0.85em; } .poster { margin: 1em; } }
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 JavaScript
/** * main.js * http://www.codrops.com * * Licensed under the MIT license. * http://www.opensource.org/licenses/mit-license.php * * Copyright 2015, Codrops * http://www.codrops.com */ ;(function(window) { 'use strict'; var support = { transitions: Modernizr.csstransitions }, // transition end event name transEndEventNames = { 'WebkitTransition': 'webkitTransitionEnd', 'MozTransition': 'transitionend', 'OTransition': 'oTransitionEnd', 'msTransition': 'MSTransitionEnd', 'transition': 'transitionend' }, transEndEventName = transEndEventNames[ Modernizr.prefixed( 'transition' ) ], onEndTransition = function( el, callback ) { var onEndCallbackFn = function( ev ) { if( support.transitions ) { if( ev.target != this ) return; this.removeEventListener( transEndEventName, onEndCallbackFn ); } if( callback && typeof callback === 'function' ) { callback.call(this); } }; if( support.transitions ) { el.addEventListener( transEndEventName, onEndCallbackFn ); } else { onEndCallbackFn(); } }, // the pages wrapper stack = document.querySelector('.pages-stack'), // the page elements pages = [].slice.call(stack.children), // total number of page elements pagesTotal = pages.length, // index of current page current = 0, // menu button menuCtrl = document.querySelector('button.menu-button'), // the navigation wrapper nav = document.querySelector('.pages-nav'), // the menu nav items navItems = [].slice.call(nav.querySelectorAll('.link--page')), // check if menu is open isMenuOpen = false; function init() { buildStack(); initEvents(); } function buildStack() { var stackPagesIdxs = getStackPagesIdxs(); // set z-index, opacity, initial transforms to pages and add class page--inactive to all except the current one for(var i = 0; i < pagesTotal; ++i) { var page = pages[i], posIdx = stackPagesIdxs.indexOf(i); if( current !== i ) { classie.add(page, 'page--inactive'); if( posIdx !== -1 ) { // visible pages in the stack page.style.WebkitTransform = 'translate3d(0,100%,0)'; page.style.transform = 'translate3d(0,100%,0)'; } else { // invisible pages in the stack page.style.WebkitTransform = 'translate3d(0,75%,-300px)'; page.style.transform = 'translate3d(0,75%,-300px)'; } } else { classie.remove(page, 'page--inactive'); } page.style.zIndex = i < current ? parseInt(current - i) : parseInt(pagesTotal + current - i); if( posIdx !== -1 ) { page.style.opacity = parseFloat(1 - 0.1 * posIdx); } else { page.style.opacity = 0; } } } // event binding function initEvents() { // menu button click menuCtrl.addEventListener('click', toggleMenu); // navigation menu clicks navItems.forEach(function(item) { // which page to open? var pageid = item.getAttribute('href').slice(1); item.addEventListener('click', function(ev) { ev.preventDefault(); openPage(pageid); }); }); // clicking on a page when the menu is open triggers the menu to close again and open the clicked page pages.forEach(function(page) { var pageid = page.getAttribute('id'); page.addEventListener('click', function(ev) { if( isMenuOpen ) { ev.preventDefault(); openPage(pageid); } }); }); // keyboard navigation events document.addEventListener( 'keydown', function( ev ) { if( !isMenuOpen ) return; var keyCode = ev.keyCode || ev.which; if( keyCode === 27 ) { closeMenu(); } } ); } // toggle menu fn function toggleMenu() { if( isMenuOpen ) { closeMenu(); } else { openMenu(); isMenuOpen = true; } } // opens the menu function openMenu() { // toggle the menu button classie.add(menuCtrl, 'menu-button--open') // stack gets the class "pages-stack--open" to add the transitions classie.add(stack, 'pages-stack--open'); // reveal the menu classie.add(nav, 'pages-nav--open'); // now set the page transforms var stackPagesIdxs = getStackPagesIdxs(); for(var i = 0, len = stackPagesIdxs.length; i < len; ++i) { var page = pages[stackPagesIdxs[i]]; page.style.WebkitTransform = 'translate3d(0, 75%, ' + parseInt(-1 * 200 - 50*i) + 'px)'; // -200px, -230px, -260px page.style.transform = 'translate3d(0, 75%, ' + parseInt(-1 * 200 - 50*i) + 'px)'; } } // closes the menu function closeMenu() { // same as opening the current page again openPage(); } // opens a page function openPage(id) { var futurePage = id ? document.getElementById(id) : pages[current], futureCurrent = pages.indexOf(futurePage), stackPagesIdxs = getStackPagesIdxs(futureCurrent); // set transforms for the new current page futurePage.style.WebkitTransform = 'translate3d(0, 0, 0)'; futurePage.style.transform = 'translate3d(0, 0, 0)'; futurePage.style.opacity = 1; // set transforms for the other items in the stack for(var i = 0, len = stackPagesIdxs.length; i < len; ++i) { var page = pages[stackPagesIdxs[i]]; page.style.WebkitTransform = 'translate3d(0,100%,0)'; page.style.transform = 'translate3d(0,100%,0)'; } // set current if( id ) { current = futureCurrent; } // close menu.. classie.remove(menuCtrl, 'menu-button--open'); classie.remove(nav, 'pages-nav--open'); onEndTransition(futurePage, function() { classie.remove(stack, 'pages-stack--open'); // reorganize stack buildStack(); isMenuOpen = false; }); } // gets the current stack pages indexes. If any of them is the excludePage then this one is not part of the returned array function getStackPagesIdxs(excludePageIdx) { var nextStackPageIdx = current + 1 < pagesTotal ? current + 1 : 0, nextStackPageIdx_2 = current + 2 < pagesTotal ? current + 2 : 1, idxs = [], excludeIdx = excludePageIdx || -1; if( excludePageIdx != current ) { idxs.push(current); } if( excludePageIdx != nextStackPageIdx ) { idxs.push(nextStackPageIdx); } if( excludePageIdx != nextStackPageIdx_2 ) { idxs.push(nextStackPageIdx_2); } return idxs; } init(); })(window);
This is innovative. Codrops Demos never cease to amaze and teach. Thank You.
Thank 1000 time
Wow very nice!!! Hvala Manoela!
Really nice. Good job.
your demo are so wonderful that they make me breaking my brain to find a good excuse to use them! Bravo Mary Lou!
Thank you for sharing, really nice
Very cool, might have to try this for one of my projects.
Wow, thanks Mary Lou! Two great items from you in the same week, just when I was suffering from ML withdrawal during your quiet spell. You never cease to amaze me with your creativity.
Very nice. Thank you.
Demo isn’t working … I’m on the latest chrome version
You need to click on the “Three Line Menu Navicon” on the top left 🙂
mary lou you are fantastic !!!!!!!
Great \m/
You are one of the most talented people I’ve ever seen in my life. Great work! This is more than great
Nice!!!
Lovely idea, thanks for the demo & source 🙂
I kept trying to figure out how to make use of the stack itself. I understood that I can click on a second or third page in a stack to open it up. However, I was unaware which page I was opening. Seems that the stack here is mostly a cosmetic feature, although as a user, I would expect it to give me a glimpse of page content (at least headings).
I am working on a similar feature, where I’m trying to stack modals from left to right. In case of many items in a stack (like with deck of cards), user should be able to hover specific one (or pull the stack) to reveal enough to make a selection.
Anyways, keep up the good work! 🙂
Maybe have a page move up a bit on hover would be nice to show a little preview. I was thinking the same thing.
fucking crazy 🙂 really awsome
Absolutely wonderful!
Looks great! How would you implement browser back navigation?
Here’s where you saw it first folks: http://ohmy.nu 😉
Nice. Like the bright yellow.
Thank you sooo sooo much! I have been experiencing a terrible time with Safari and I just didn’t know what to do anymore. The site you suggested is just life saving. Thank you so much for pointing that out. I’m going to try and add a few more pages once you click on the menu but even as is, it’s enough.
It´s amazing and creative, thanks for share!
Awesome..
Really great. Love it.
It would be awesome if in the stacked view you can see what the other pages behind the front one are. This way it would be easy to know what you are selecting from the stack too.
Try styling the pages with different background colors/images.
Amazing work! Thanks for sharing 🙂
Great and it’s awesome!
Great design! However, I was testing on Safari (5.1.7)on Windows 7 and the navigation isn’t working properly. Have you a fix for this?
This is brilliant! Awesome blueprint. Funny thing though, I tried to use it with only three pages and when I am on the second page, if I click the menu button again, the stack is broken and only displays 2 pages and in the wrong stacking order but when I am on the first and second, the stack looks good. Anyone have any ideas whats wrong?
How would this be implemented within a multi-page layout? I love the UX, but the client needs a multi-page site for SEO.
I want to avoid flicker issues with redrawing the page as much as possible, and I think that maybe the best way is after loading the selected divpage I instead load the separate page once the animation completes?
Hi Cameron,
Did you find any solution for multi page with SEO friendly. Please share it if you find something.
This is great. I think I’ll stop using wordpress.
This is great! I’m playing around with the settings but I cannot make a section to scroll to show the rest of the content on it. Is there another way use it instead of the overflow=hidden?
If you want to allow your content pages to scroll, simply modify main.js and add
page.style.overflow = 'scroll';
to line 80 right afterclassie.remove(page, 'page--inactive');
.Is there anyway to increase length of the page? Currently it is equal to screen length of device. I want to make it scroll. Actually my idea is make a main landing page and use above nav just for linking to parts of my landing page.
Any luck with this Abid?
A bit old but I sorted some of this out just with CSS. You’d need an additional class added to html.js & .js body with height: 100vh & overflow:hidden when the menu is open to prevent scrolling. And a way to hide the inactive pages which I haven’t sorted out yet.
html.js, .js body {
overflow: visible;
height: auto;
}
.pages-stack–open .page {
height: 100vh;
}
.js .page {
position: relative;
z-index: 5;
overflow: hidden;
width: auto;
pointer-events: auto;
background: #2a2b30;
box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
height: auto;
min-height: 100vh;
}
Is there any way to implement anchors in the content that lead to a other page just like the anchors in the navigation? I want to place teasers in the content of one page that link to the other. But the script only slices the anchors from the URL if they are located in the navigation. So is there a way to change that?
@Nik – Were you ever able to get this working? I am also trying to figure out how to link to other “pages” from within the content. (without sending user through the navigation).
Did you ever get this? Still trying to figure this out myself. JS not my strong-suit.
Hey guys,
did anyone find out how to reduce the distance between the expanded menu and the stack preview in mobile view?
Greatly appreciate any help,
Daniel
Is there any way i can place links to internal pages into the section pages? not only in the nav menu
Do you found how I can place links to internal pages into the section pages ?
Thanks
I am also trying to do this. Has anyone been able to figure out how to link to the other pages without using the nav?
i failed to add any code of php T.T ,the menu bar was hide
Hi there! Love your site and all the content everyone creates and shares.
I have been playing around with this theme for a business site, and am running into an issue with changing the color of the Main Menu Bar Links. I have been able to change the base color and also the Focus color but this breaks the function of the button. I change the class of the button by copying from the existing class but I am thinking I am missing a part to copy and the result is the new class does not have all of the formatting needed from the CSS to function. Any help would be greatly appreciated!
awesome stuff lou..well done keep it going…
There are any solution to scroll the content?
This doesn’t work at all in Internet Explorer 10 or 11, or in Safari 5.1.7 on Windows 7.
Any ideas on how to make this backwards compatible?
UPDATE: Looks like I had to enable javascript to run in IE, in which case it works fine.
Safari 5.1.7 doens’t work, though.
Well, this does not work in the default Android browser. And some Safaris.
Or is the page opens after a redirection. The main layout depedns on JS, this looks amazing, but not ready for production.
Very nice! I would suggest ev.stopPropagation() after calling toggleMenu, took me a bit of time to understand why
I couldn’t trigger the menu from a page content. Anyway very nice layout thx! 🙂
@Pa – Can you provide an example. I think I’m trying to do the same thing you’re suggesting, but I can’t get it to work.
Did anyone ever find out how to link to one of the “pages” from within the content area. (so you don’t have to select the nav and then the item you’re trying to go to?)
Add this to the end of main.js, but inside the })(window); part:
window.changePage = function(pageid){ toggleMenu(); openPage(pageid); toggleMenu(); }
Then from your code call changePage(‘href-part’); it says id which may work, but internally it uses eg <a href="href-part"…
if you don't call toggleMenu you will have to add some of the code from inside toggleMenu() that keeps transitions up-to-date.
I ended up creating a proper solution to this issue: https://github.com/steveseeger/PageStackNavigation and I have created a pull request to merge the feature. You can see the new code here: https://github.com/codrops/PageStackNavigation/pull/2/files
How could I modify the page such that it automatically hides the browsers Address bar (on mobile) once the page is loaded?
Did anyone ever discover a proper fix to allow for vertical scrolling, whilst hiding inactive pages? The earlier suggestions are incomplete.
Is it possible to call a specific ‘page’ via an external link url? I’ve tried http://mypagestacksite.foo/#id_of_page_name but this loads all the pages overlaid.