Forked from Keenan Staffieri's Pen ES6 Modal Menu.
Created
March 14, 2016 20:50
-
-
Save JBreit/7c65354b674c6ddf1702 to your computer and use it in GitHub Desktop.
ES6 Modal Menu
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<nav class="top-nav"> | |
<p>Click on the 'Navigate' button to open modal.</p> | |
<div id="burger-menu" class="burger-menu"> | |
<div id="burger-click-region" class="burger-click-region"> | |
<span class="burger-menu-piece"></span> | |
<span class="burger-menu-piece"></span> | |
<span class="burger-menu-piece"></span> | |
</div> | |
<span class="burger-menu-txt">Navigate</span> | |
</div> | |
<div class="ks-modal-contents"> | |
<nav id="site-menu-contents" class="site-nav"> | |
<ul class="site-nav-list"> | |
<li><a href="#">Skills</a></li> | |
<li><a href="#">Services</a></li> | |
<li><a href="#">Work</a></li> | |
<li><a href="#">Blog</a></li> | |
</ul> | |
</nav> | |
</div> | |
</nav> <!-- .top-nav --> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class KS_Modal { | |
constructor(selectorName, modalContentsSelector, clickRegionElement=null) { | |
this.self = document.querySelector(selectorName); | |
this.modalContents = document.querySelector(modalContentsSelector); | |
this.clickRegionElement = document.querySelector(clickRegionElement); | |
this.isOpen = false; | |
this.openDelay = 500; | |
this.openDelayTimer = null; | |
// Initialize modal | |
this._init(); | |
} | |
_init() { | |
// Create node ID to hold the markup for this modal instance | |
var modalNodeInstance = document.createElement('div'); | |
modalNodeInstance.id = 'ks-modal-instance-' + Math.floor(Math.random()*100000); | |
// Create modal container element | |
this.modalEle = document.createElement('div'); | |
this.modalEle.classList.add('ks-modal'); | |
// Create modal inner container | |
var modalInnerEle = document.createElement('div'); | |
modalInnerEle.classList.add('ks-modal-inner'); | |
// Clone modal contents into inner modal container | |
var modalContents = this.modalContents.cloneNode(true); | |
modalInnerEle.appendChild(modalContents); | |
// Add modal node instance to the DOM | |
document.body.appendChild(modalNodeInstance); | |
// Add inner container as a child to the modal element | |
this.modalEle.appendChild(modalInnerEle); | |
// Add modal element to the instance container | |
modalNodeInstance.appendChild(this.modalEle); | |
// Get click region element | |
var clickRegionElement; | |
if(this.clickRegionElement === null) { | |
clickRegionElement = this.self; | |
} | |
else { | |
clickRegionElement = this.clickRegionElement; | |
} | |
// Add event listeners for modal | |
clickRegionElement.addEventListener('click', () => { | |
if(this.isOpen) { | |
this.close(); | |
} | |
else { | |
this.open(); | |
} | |
}); | |
} | |
open() { | |
// Only open the modal if delay timer expired | |
if(this.openDelayTimer === null) { | |
this.isOpen = true; | |
this.self.classList.add('is-open'); | |
this.modalEle.classList.add('open'); | |
if(this.clickRegionElement !== null) { | |
this.clickRegionElement.classList.add('active'); | |
} | |
this.openDelayTimer = setTimeout( () => { | |
clearTimeout(this.openDelayTimer); | |
this.openDelayTimer = null; | |
}, this.openDelay); | |
} | |
} | |
close() { | |
// Only close the modal if delay timer expired | |
if(this.openDelayTimer === null) { | |
this.isOpen = false; | |
this.self.classList.remove('is-open'); | |
this.modalEle.classList.remove('open'); | |
if(this.clickRegionElement !== null) { | |
this.clickRegionElement.classList.remove('active'); | |
this.clickRegionElement.classList.add('closing'); | |
} | |
this.openDelayTimer = setTimeout( () => { | |
if(this.clickRegionElement !== null) { | |
this.clickRegionElement.classList.remove('closing'); | |
} | |
clearTimeout(this.openDelayTimer); | |
this.openDelayTimer = null; | |
}, this.openDelay); | |
} | |
} | |
} | |
// Create modal instance... | |
var menuModal = new KS_Modal('#burger-menu', '#site-menu-contents', '#burger-click-region'); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
@import "bourbon"; | |
@import url(http://fonts.googleapis.com/css?family=Montserrat); | |
body { | |
background-color: #1a1a1a; | |
font-family: 'Montserrat', sans-serif; | |
} | |
.top-nav { | |
position: relative; | |
height: 60px; | |
background-color: black; | |
} | |
.top-nav p { | |
position: absolute; | |
left: 20px; | |
top: 8px; | |
color: #666; | |
font-size: 14px; | |
} | |
.burger-menu { | |
position: absolute; | |
right: 20px; | |
top: 16px; | |
width: 140px; | |
height: 30px; | |
color: #60635E; | |
text-transform: uppercase; | |
z-index: 100; | |
&.is-open { | |
z-index: 1010; | |
} | |
} | |
.burger-click-region { | |
position: absolute; | |
left: 100px; | |
width: 40px; | |
height: 30px; | |
cursor: pointer; | |
} | |
.burger-menu-txt { | |
display: block; | |
position: absolute; | |
left: 0; | |
top: 5px; | |
cursor: default; | |
transition: opacity 200ms ease-out, transform 200ms cubic-bezier(.34, .55, .25, .83); | |
.is-open & { | |
opacity: 0; | |
transform: translate3d(-50px, 0, 0); | |
} | |
} | |
$menu-animation-duration: 400ms; | |
$menu-animation-timing: ease-out; | |
.burger-menu-piece { | |
display: block; | |
position: absolute; | |
width: 40px; | |
border-top: 6px solid #C2C2C2; | |
transform-origin: 50% 50%; | |
transition: transform $menu-animation-duration $menu-animation-timing; | |
&:nth-child(1) { | |
top: 0; | |
} | |
&:nth-child(2) { | |
top: 12px; | |
opacity: 1; | |
transition: transform $menu-animation-duration $menu-animation-timing, opacity 0ms linear $menu-animation-duration / 2; | |
} | |
&:nth-child(3) { | |
top: 24px; | |
} | |
.active & { | |
&:nth-child(1) { | |
animation: burger-open-top $menu-animation-duration $menu-animation-timing forwards; | |
} | |
&:nth-child(2) { | |
opacity: 0; | |
transition: transform $menu-animation-duration $menu-animation-timing, opacity 0ms linear $menu-animation-duration / 2; | |
} | |
&:nth-child(3) { | |
animation: burger-open-bot $menu-animation-duration $menu-animation-timing forwards; | |
} | |
} | |
.closing & { | |
&:nth-child(1) { | |
animation: burger-close-top $menu-animation-duration $menu-animation-timing forwards; | |
} | |
&:nth-child(3) { | |
animation: burger-close-bot $menu-animation-duration $menu-animation-timing forwards; | |
} | |
} | |
} | |
@keyframes burger-open-top { | |
50% { | |
transform: translate3d(0, 12px, 0); | |
} | |
100% { | |
transform: translate3d(0, 12px, 0) rotate(45deg); | |
} | |
} | |
@keyframes burger-open-bot { | |
50% { | |
transform: translate3d(0, -12px, 0); | |
} | |
100% { | |
transform: translate3d(0, -12px, 0) rotate(-45deg); | |
} | |
} | |
@keyframes burger-close-top { | |
0% { | |
transform: translate3d(0, 12px, 0) rotate(45deg); | |
} | |
50% { | |
transform: translate3d(0, 12px, 0) rotate(0deg); | |
} | |
100% { | |
transform: translate3d(0, 0, 0); | |
} | |
} | |
@keyframes burger-close-bot { | |
0% { | |
transform: translate3d(0, -12px, 0) rotate(-45deg); | |
} | |
50% { | |
transform: translate3d(0, -12px, 0) rotate(0deg); | |
} | |
100% { | |
transform: translate3d(0, 0, 0); | |
} | |
} | |
.ks-modal { | |
position: absolute; | |
left: 0; | |
top: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 0, 0, 0.8); | |
opacity: 0; | |
visibility: hidden; | |
z-index: 1000; | |
transition: opacity 500ms ease-out, visibility 0 linear 500ms; | |
&.open { | |
opacity: 1; | |
visibility: visible; | |
transition: opacity 500ms ease-out, visibility 0 linear 0; | |
} | |
} | |
.ks-modal-contents { | |
display: none; | |
} | |
.site-nav-list { | |
position: absolute; | |
top: 15%; | |
width: 100%; | |
list-style: none; | |
padding: 0; | |
margin: 0; | |
li { | |
$menu-item-1-delay: 140ms; | |
$menu-item-2-delay: 100ms; | |
$menu-item-3-delay: 70ms; | |
$menu-item-4-delay: 50ms; | |
margin: 20px 0; | |
opacity: 0; | |
transform: translate3d(0, 50px, 0); | |
&:nth-child(1) { | |
transition: opacity 250ms ease-out $menu-item-1-delay, transform 250ms ease-in $menu-item-1-delay; | |
} | |
&:nth-child(2) { | |
transition: opacity 250ms ease-out $menu-item-2-delay, transform 250ms ease-in $menu-item-2-delay; | |
} | |
&:nth-child(3) { | |
transition: opacity 250ms ease-out $menu-item-3-delay, transform 250ms ease-in $menu-item-3-delay; | |
} | |
&:nth-child(4) { | |
transition: opacity 500ms ease-out $menu-item-4-delay, transform 400ms ease-out $menu-item-4-delay; | |
} | |
} | |
li a { | |
display: block; | |
color: white; | |
font-size: 40px; | |
text-align: center; | |
text-decoration: none; | |
transition: color 200ms ease-in; | |
&:hover { | |
color: magenta; | |
} | |
} | |
} | |
.ks-modal.open { | |
.site-nav-list li { | |
$menu-item-1-delay: 250ms; | |
$menu-item-2-delay: 270ms; | |
$menu-item-3-delay: 300ms; | |
$menu-item-4-delay: 340ms; | |
opacity: 1; | |
transform: translate3d(0, 0, 0); | |
&:nth-child(1) { | |
transition: opacity 500ms ease-out $menu-item-1-delay, transform 400ms ease-out $menu-item-1-delay; | |
} | |
&:nth-child(2) { | |
transition: opacity 500ms ease-out $menu-item-2-delay, transform 400ms ease-out $menu-item-2-delay; | |
} | |
&:nth-child(3) { | |
transition: opacity 500ms ease-out $menu-item-3-delay, transform 400ms ease-out $menu-item-3-delay; | |
} | |
&:nth-child(4) { | |
transition: opacity 500ms ease-out $menu-item-4-delay, transform 400ms ease-out $menu-item-4-delay; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment