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