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; } }
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);