Off-canvas Side Navigation With Page Transitions
AUTHOR: | Kyle Brumm |
---|---|
VIEWS TOTAL: | 1,003 views |
OFFICIAL PAGE: | Go to website |
LAST UPDATE: | February 7, 2018 |
LICENSE: | MIT |
Preview:
Description:
A modern sticky off-canvas sidebar navigation where the users are able to switch between page sections with a smooth transition effect by clicking nav links.
How to use it:
Create the off-canvas navigation that contains anchor links pointing to their page sections:
<nav class="nav"> <ul class="nav__list"> <li class="nav__item"><a href="#">Section 1</a></li> <li class="nav__item"><a href="#">Section 2</a></li> <li class="nav__item"><a href="#">Section 3</a></li> ... </ul> </nav>
Create a trigger element to toggle the off-canvas navigation.
<div class="nav__bar"> <a href="#" class="nav__trigger"> <div class="bars"></div> </a> </div>
Create the sectioned content as follows:
<main class="main"> <section class="content"> <article class="article"> <a href class="article__title">Section 1</a> <p class="article__content"> Section 1 Content </p> </article> <article class="article"> <a href class="article__title">Section 2</a> <p class="article__content"> Section 2 Content </p> </article> <article class="article"> <a href class="article__title">Section 3</a> <p class="article__content"> Section 3 Content </p> </article> ... </section> </main>
The primary CSS/CSS3 styles.
*, *:before, *:after { -webkit-box-sizing: border-box; box-sizing: border-box; } html { font-family: "Open Sans", Helvetica, arial, sans-serif; color: #333333; background-color: #eeeeee; } body.is-froze { overflow: hidden; width: 100vw; height: 100vh; } h1, h2, h3, h4, h5, h6 { font-family: "Raleway", "Open Sans", sans-serif; } a { color: #333333; text-decoration: none; } img { max-width: 100%; } a { -webkit-transition: color 0.3s ease-in-out; transition: color 0.3s ease-in-out; } a:hover { color: #7d87a8; } .main { overflow: hidden; position: relative; width: 100%; width: calc(100% - 60px); height: 100vh; margin-left: 60px; background-color: #eeeeee; -webkit-transition: 0.55s cubic-bezier(0.645, 0.045, 0.355, 1); transition: 0.55s cubic-bezier(0.645, 0.045, 0.355, 1); -webkit-transform: scale(1) translate3d(0, 0, 0); transform: scale(1) translate3d(0, 0, 0); -webkit-clip-path: inset(0 0 0 0); clip-path: inset(0 0 0 0); will-change: width, height, opacity, transform, clip-path; z-index: 1; } .main.is-active { overflow: hidden; height: 100vh; width: 100vw; width: calc(100vw - 60px); pointer-events: none; opacity: 0.25; -webkit-transform: scale(0.9) translate3d(60%, 0, 0); transform: scale(0.9) translate3d(60%, 0, 0); } @media (min-width: 600px) { .main.is-active { -webkit-transform: scale(0.9) translate3d(40%, 0, 0); transform: scale(0.9) translate3d(40%, 0, 0); } } .main.is-transition-out { -webkit-clip-path: inset(0 0 0 100%); clip-path: inset(0 0 0 100%); } .article { padding: 1.5rem; position: relative; } @media (min-width: 600px) { .article { padding: 6vmin; } } .article:not(:last-of-type):after { content: ''; position: absolute; bottom: 0; left: 1.5rem; width: 50px; height: 2px; background-color: #7d87a8; } @media (min-width: 600px) { .article:not(:last-of-type):after { left: 6vmin; } } .article__title { display: block; position: relative; font-family: "Raleway", "Open Sans", sans-serif; font-size: 1.5rem; color: #191b22; } @media (min-width: 600px) { .article__title { font-size: 3vmin; } } .article__title:hover { color: #7d87a8; } .article__time { display: block; position: relative; text-transform: uppercase; font-size: 0.8rem; margin-top: 1rem; } @media (min-width: 600px) { .article__time { font-size: 1.5vmin; } } .article__content { margin: 1rem 0 0; font-size: 1rem; line-height: 1.5; } @media (min-width: 600px) { .article__content { font-size: 2vmin; } } .nav__bar { position: fixed; top: 0; bottom: 0; left: 0; width: 60px; height: 100vh; border-right: 1px solid rgba(125, 135, 168, 0.25); background-color: #191b22; z-index: 99; } .nav__trigger { display: block; position: absolute; top: 50%; left: 16px; padding: 8px 0; margin-top: -10px; -webkit-transition: 0.2s ease-in-out; transition: 0.2s ease-in-out; z-index: 99; } .nav__trigger .bars { position: relative; } .nav__trigger .bars, .nav__trigger .bars:before, .nav__trigger .bars:after { width: 28px; height: 4px; background-color: #7d87a8; -webkit-transition: 0.2s ease-in-out; transition: 0.2s ease-in-out; border-radius: 4px; } .nav__trigger .bars:before, .nav__trigger .bars:after { content: ''; display: block; position: absolute; top: 0; left: 0; will-change: transform; } .nav__trigger .bars:before { -webkit-transform: translateY(-8px); transform: translateY(-8px); } .nav__trigger .bars:after { -webkit-transform: translateY(8px); transform: translateY(8px); } .nav__trigger.is-active { -webkit-transform: rotate(-45deg); transform: rotate(-45deg); } .nav__trigger.is-active .bars:before, .nav__trigger.is-active .bars:after { -webkit-transform: translateX(0) rotate(-90deg); transform: translateX(0) rotate(-90deg); } .nav { position: fixed; top: 0; right: 0; bottom: 0; left: 0; background-color: #191b22; z-index: 0; } .nav__list { overflow: hidden; position: absolute; top: 50%; left: 0; width: 100%; margin: 0; padding-left: 60px; list-style: none; font-family: "Raleway", "Open Sans", sans-serif; -webkit-transform: translateY(-50%); transform: translateY(-50%); } .nav__list .nav__item { padding: 0.5rem 1rem; } @media (min-width: 600px) { .nav__list .nav__item { width: 33.3333333333%; padding: 0.5rem 1rem; } } .nav__list a { display: inline-block; color: #7d87a8; font-size: 1rem; line-height: 1.5; } .nav__list a:hover { color: #b1b7cb; } .nav__list a.is-active { color: #d2d5e1; } @media (min-width: 600px) { .nav__list a { font-size: 1.5rem; } }
Load the necessary classList.js and smoothScroll libraries in the document.
<script src='https://cdnjs.cloudflare.com/ajax/libs/classlist/2014.01.31/classList.min.js'></script> <script src='https://cdnjs.cloudflare.com/ajax/libs/iamdustan-smoothscroll/0.4.0/smoothscroll.js'></script>
The main JavaScript to activate the off-canvas navigation & smooth page transition effects.
let navigation = { // Variables $navTrigger: document.querySelector('.nav__trigger'), $nav: document.querySelector('.nav'), $navItems: document.querySelectorAll('.nav__item a'), $main: document.querySelector('.main'), transitionEnd: 'webkitTransitionEnd otransitionend oTransitionEnd msTransitionEnd transitionend', isOpeningNav: false, init() { let self = this; // Reset overflow and height on load self.$main.style.overflow = 'auto'; self.$main.style.height = 'auto'; // Handle scroll events window.addEventListener('scroll', (e) => { if (window.scrollY == 0 && self.isOpeningNav) { self.isOpeningNav = false; // Add a small delay setTimeout(function() { self.openNavigation(); }, 150); } }); // Handle .nav__trigger click event self.$navTrigger.addEventListener('click', (e) => { e.preventDefault(); if (!self.$navTrigger.classList.contains('is-active')) { if (window.scrollY !== 0) { // Scroll to top window.scroll({ top: 0, left: 0, behavior: 'smooth' }); // Enable opening nav self.isOpeningNav = true; } else { self.openNavigation(); } } else { self.closeNavigation(); } }); // Handle .nav__item click events self.$navItems.forEach((navLink) => { navLink.addEventListener('click', function(e) { e.preventDefault(); // Remove is-active from all .nav__items self.$navItems.forEach((el) => { el.classList.remove('is-active'); }); // Ad is-active to clicked .nav__item this.classList.add('is-active'); // Transition the page self.transitionPage(); }); }); }, openNavigation() { let self = this; // .nav--trigger active self.$navTrigger.classList.add('is-active'); // body froze document.body.classList.add('is-froze'); // Remove old inline styles if (self.$main.style.removeProperty) { self.$main.style.removeProperty('overflow'); self.$main.style.removeProperty('height'); } else { self.$main.style.removeAttribute('overflow'); self.$main.style.removeAttribute('height'); } // .main active self.$main.classList.add('is-active'); }, closeNavigation() { let self = this; // .nav--trigger inactive self.$navTrigger.classList.remove('is-active'); // .main inactive self.$main.classList.remove('is-active'); self.$main.addEventListener('transitionend', (e) => { if (e.propertyName == 'transform' && !self.$navTrigger.classList.contains('is-active')) { // Reset overflow and height self.$main.style.overflow = 'auto'; self.$main.style.height = 'auto'; // body unfroze document.body.classList.remove('is-froze'); } }); // no-csstransitions fallback if (document.documentElement.classList.contains('no-csstransitions')) { // .main inactive self.$main.classList.remove('is-active'); // body unfroze document.body.classList.remove('is-froze'); } }, transitionPage() { let self = this; // .main transitioning self.$main.classList.add('is-transition-out'); self.$main.addEventListener('transitionend', (e) => { if (e.propertyName == 'clip-path') { if (self.$main.classList.contains('is-transition-in')) { self.$main.classList.remove('is-transition-in'); self.$main.classList.remove('is-transition-out'); self.closeNavigation(); } if (self.$main.classList.contains('is-transition-out')) { self.$main.classList.remove('is-transition-out'); // Add new content to .main setTimeout(function() { self.$main.classList.add('is-transition-in'); }, 500); } } }); } } navigation.init();