Material Design inspired modals. No jQuery required. Responsive. Read the how to on Ettrics.com
A Pen by Polarhardboiled on CodePen.
<div class="demo-btns"> | |
<header> | |
<h1>Material Design Modals</h1> | |
</header> | |
<div class="info"> | |
<div class="buttons"> | |
<p> | |
<a href="" data-modal="#modal" class="modal__trigger">Modal 1</a> | |
<a href="" data-modal="#modal2" class="modal__trigger">Modal 2</a> | |
<a href="" data-modal="#modal3" class="modal__trigger">Modal 3</a> | |
</p> | |
</div> | |
<p>Click a button to activate a modal.</p> | |
</div> | |
</div> | |
<!-- Modal --> | |
<div id="modal" class="modal modal__bg" role="dialog" aria-hidden="true"> | |
<div class="modal__dialog"> | |
<div class="modal__content"> | |
<h1>Modal</h1> | |
<p>Church-key American Apparel trust fund, cardigan mlkshk small batch Godard mustache pickled bespoke meh seitan. Wes Anderson farm-to-table vegan, kitsch Carles 8-bit gastropub paleo YOLO jean shorts health goth lo-fi. Normcore chambray locavore Banksy, YOLO meditation master cleanse readymade Bushwick.</p> | |
<!-- modal close button --> | |
<a href="" class="modal__close demo-close"> | |
<svg class="" viewBox="0 0 24 24"><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"/><path d="M0 0h24v24h-24z" fill="none"/></svg> | |
</a> | |
</div> | |
</div> | |
</div> | |
<div id="modal2" class="modal modal--align-top modal__bg" role="dialog" aria-hidden="true"> | |
<div class="modal__dialog"> | |
<div class="modal__content"> | |
<h1>Big Modal</h1> | |
<h3>This modal is pretty tall.</h3> | |
<p>Selfies normcore four dollar toast four loko listicle artisan. Hoodie Marfa authentic, wayfarers church-key tofu Banksy pop-up Kickstarter Brooklyn heirloom swag synth. Echo Park cray synth mixtape. Tofu gastropub squid readymade, trust fund Wes Anderson DIY PBR 8-bit try-hard +1 Shoreditch lo-fi tote bag.</p> | |
<p><img src="http://unsplash.it/600/300" alt="" /></p> | |
<p>Mumblecore cred selfies fingerstache. Tousled skateboard plaid lo-fi shabby chic salvia, swag Odd Future Etsy art party Austin cronut. Crucifix whatever Pinterest food truck, pickled viral cray 90's DIY chambray keffiyeh biodiesel Vice blog. Cred meh yr tofu.</p> | |
<p>Mumblecore cred selfies fingerstache. Tousled skateboard plaid lo-fi shabby chic salvia, swag Odd Future Etsy art party Austin cronut. Crucifix whatever Pinterest food truck, pickled viral cray 90's DIY chambray keffiyeh biodiesel Vice blog. Cred meh yr tofu.</p> | |
<!-- modal close button --> | |
<a href="" class="modal__close demo-close"> | |
<svg class="" viewBox="0 0 24 24"><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"/><path d="M0 0h24v24h-24z" fill="none"/></svg> | |
</a> | |
</div> | |
</div> | |
</div> | |
<div id="modal3" class="modal modal__bg" role="dialog" aria-hidden="true"> | |
<div class="modal__dialog"> | |
<div class="modal__content"> | |
<h1>Modal 3</h1> | |
<p>Church-key American Apparel trust fund, cardigan mlkshk small batch Godard mustache pickled bespoke meh seitan. Wes Anderson farm-to-table vegan, kitsch Carles 8-bit gastropub paleo YOLO jean shorts health goth lo-fi.</p> | |
<!-- modal close button --> | |
<a href="" class="modal__close demo-close"> | |
<svg class="" viewBox="0 0 24 24"><path d="M19 6.41l-1.41-1.41-5.59 5.59-5.59-5.59-1.41 1.41 5.59 5.59-5.59 5.59 1.41 1.41 5.59-5.59 5.59 5.59 1.41-1.41-5.59-5.59z"/><path d="M0 0h24v24h-24z" fill="none"/></svg> | |
</a> | |
</div> | |
</div> | |
</div> | |
<!-- Ettrics --> | |
<a href="https://ettrics.com/" class="logo" target="_blank"> | |
<img class="logo" src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/45226/ettrics-logo.svg" alt="" /> | |
</a> |
Material Design inspired modals. No jQuery required. Responsive. Read the how to on Ettrics.com
A Pen by Polarhardboiled on CodePen.
var Modal = (function() { | |
var trigger = $qsa('.modal__trigger'); // what you click to activate the modal | |
var modals = $qsa('.modal'); // the entire modal (takes up entire window) | |
var modalsbg = $qsa('.modal__bg'); // the entire modal (takes up entire window) | |
var content = $qsa('.modal__content'); // the inner content of the modal | |
var closers = $qsa('.modal__close'); // an element used to close the modal | |
var w = window; | |
var isOpen = false; | |
var contentDelay = 400; // duration after you click the button and wait for the content to show | |
var len = trigger.length; | |
// make it easier for yourself by not having to type as much to select an element | |
function $qsa(el) { | |
return document.querySelectorAll(el); | |
} | |
var getId = function(event) { | |
event.preventDefault(); | |
var self = this; | |
// get the value of the data-modal attribute from the button | |
var modalId = self.dataset.modal; | |
var len = modalId.length; | |
// remove the '#' from the string | |
var modalIdTrimmed = modalId.substring(1, len); | |
// select the modal we want to activate | |
var modal = document.getElementById(modalIdTrimmed); | |
// execute function that creates the temporary expanding div | |
makeDiv(self, modal); | |
}; | |
var makeDiv = function(self, modal) { | |
var fakediv = document.getElementById('modal__temp'); | |
/** | |
* if there isn't a 'fakediv', create one and append it to the button that was | |
* clicked. after that execute the function 'moveTrig' which handles the animations. | |
*/ | |
if (fakediv === null) { | |
var div = document.createElement('div'); | |
div.id = 'modal__temp'; | |
self.appendChild(div); | |
moveTrig(self, modal, div); | |
} | |
}; | |
var moveTrig = function(trig, modal, div) { | |
var trigProps = trig.getBoundingClientRect(); | |
var m = modal; | |
var mProps = m.querySelector('.modal__content').getBoundingClientRect(); | |
var transX, transY, scaleX, scaleY; | |
var xc = w.innerWidth / 2; | |
var yc = w.innerHeight / 2; | |
// this class increases z-index value so the button goes overtop the other buttons | |
trig.classList.add('modal__trigger--active'); | |
// these values are used for scale the temporary div to the same size as the modal | |
scaleX = mProps.width / trigProps.width; | |
scaleY = mProps.height / trigProps.height; | |
scaleX = scaleX.toFixed(3); // round to 3 decimal places | |
scaleY = scaleY.toFixed(3); | |
// these values are used to move the button to the center of the window | |
transX = Math.round(xc - trigProps.left - trigProps.width / 2); | |
transY = Math.round(yc - trigProps.top - trigProps.height / 2); | |
// if the modal is aligned to the top then move the button to the center-y of the modal instead of the window | |
if (m.classList.contains('modal--align-top')) { | |
transY = Math.round(mProps.height / 2 + mProps.top - trigProps.top - trigProps.height / 2); | |
} | |
// translate button to center of screen | |
trig.style.transform = 'translate(' + transX + 'px, ' + transY + 'px)'; | |
trig.style.webkitTransform = 'translate(' + transX + 'px, ' + transY + 'px)'; | |
// expand temporary div to the same size as the modal | |
div.style.transform = 'scale(' + scaleX + ',' + scaleY + ')'; | |
div.style.webkitTransform = 'scale(' + scaleX + ',' + scaleY + ')'; | |
window.setTimeout(function() { | |
window.requestAnimationFrame(function() { | |
open(m, div); | |
}); | |
}, contentDelay); | |
}; | |
var open = function(m, div) { | |
if (!isOpen) { | |
// select the content inside the modal | |
var content = m.querySelector('.modal__content'); | |
// reveal the modal | |
m.classList.add('modal--active'); | |
// reveal the modal content | |
content.classList.add('modal__content--active'); | |
/** | |
* when the modal content is finished transitioning, fadeout the temporary | |
* expanding div so when the window resizes it isn't visible ( it doesn't | |
* move with the window). | |
*/ | |
content.addEventListener('transitionend', hideDiv, false); | |
isOpen = true; | |
} | |
function hideDiv() { | |
// fadeout div so that it can't be seen when the window is resized | |
div.style.opacity = '0'; | |
content.removeEventListener('transitionend', hideDiv, false); | |
} | |
}; | |
var close = function(event) { | |
event.preventDefault(); | |
event.stopImmediatePropagation(); | |
var target = event.target; | |
var div = document.getElementById('modal__temp'); | |
/** | |
* make sure the modal__bg or modal__close was clicked, we don't want to be able to click | |
* inside the modal and have it close. | |
*/ | |
if (isOpen && target.classList.contains('modal__bg') || target.classList.contains('modal__close')) { | |
// make the hidden div visible again and remove the transforms so it scales back to its original size | |
div.style.opacity = '1'; | |
div.removeAttribute('style'); | |
/** | |
* iterate through the modals and modal contents and triggers to remove their active classes. | |
* remove the inline css from the trigger to move it back into its original position. | |
*/ | |
for (var i = 0; i < len; i++) { | |
modals[i].classList.remove('modal--active'); | |
content[i].classList.remove('modal__content--active'); | |
trigger[i].style.transform = 'none'; | |
trigger[i].style.webkitTransform = 'none'; | |
trigger[i].classList.remove('modal__trigger--active'); | |
} | |
// when the temporary div is opacity:1 again, we want to remove it from the dom | |
div.addEventListener('transitionend', removeDiv, false); | |
isOpen = false; | |
} | |
function removeDiv() { | |
setTimeout(function() { | |
window.requestAnimationFrame(function() { | |
// remove the temp div from the dom with a slight delay so the animation looks good | |
div.remove(); | |
}); | |
}, contentDelay - 50); | |
} | |
}; | |
var bindActions = function() { | |
for (var i = 0; i < len; i++) { | |
trigger[i].addEventListener('click', getId, false); | |
closers[i].addEventListener('click', close, false); | |
modalsbg[i].addEventListener('click', close, false); | |
} | |
}; | |
var init = function() { | |
bindActions(); | |
}; | |
return { | |
init: init | |
}; | |
}()); | |
Modal.init(); |
$modal-z = 1000 | |
$modal-bg = #FFEBEE | |
$modal-width = 600px | |
$space = 2.4rem | |
$red = #F44336 | |
$t = .5s | |
$ease($s = $t) | |
transition all $s cubic-bezier(0.23, 1, 0.32, 1) | |
* | |
box-sizing border-box | |
body | |
line-height 1.5 | |
font-family 'Lato' | |
-webkit-font-smoothing antialiased | |
overflow-x hidden | |
h1, h2, h3, p | |
font-weight 300 | |
margin 0 0 ($space) 0 | |
h1, h2, h3 | |
line-height 1.3 | |
a | |
text-decoration none | |
color inherit | |
font-weight 400 | |
/** | |
* Material Modal CSS | |
*/ | |
.modal | |
will-change visibility, opacity | |
display flex | |
align-items center | |
justify-content center | |
position fixed | |
top 0 | |
left 0 | |
right 0 | |
bottom 0 | |
overflow-y auto | |
overflow-x hidden | |
z-index $modal-z | |
// hide modal | |
visibility hidden | |
opacity 0 | |
$ease() | |
transition-delay $modal-delay | |
&--active | |
// reveal modal | |
visibility visible | |
opacity 1 | |
&--align-top | |
// align to top of window, useful if modal has a lot of content | |
align-items flex-start | |
&__bg | |
// background color can be added as a backdrop for the modal | |
background transparent | |
&__dialog | |
// controls the width and padding of modal | |
max-width $modal-width | |
padding ($space / 2) | |
&__content | |
will-change transform, opacity | |
position relative | |
padding $space | |
background $modal-bg | |
background-clip padding-box | |
box-shadow 0 12px 15px 0 rgba(black, 0.25) | |
opacity 0 | |
$ease(.25s) | |
&--active | |
opacity 1 | |
&__close | |
z-index $modal-z+100 | |
cursor pointer | |
&__trigger | |
// modal trigger button | |
position relative | |
display inline-block; | |
padding ($space / 2) $space | |
color: rgba(black, 0.7) | |
line-height 1 | |
cursor pointer | |
background $modal-bg | |
box-shadow 0 2px 5px 0 rgba(black, 0.26) | |
-webkit-tap-highlight-color: rgba(0,0,0,0); user-select none | |
$ease() | |
&--active | |
z-index 10 | |
&:hover | |
background mix(black, $modal-bg, 10) | |
#modal__temp | |
// this is the div that expands when the button is clicked | |
will-change transform, opacity | |
position absolute | |
top 0 | |
left 0 | |
right 0 | |
bottom 0 | |
background $modal-bg | |
transform none | |
opacity 1 | |
transition opacity 0.1s ease-out, transform $t cubic-bezier(0.23, 1, 0.32, 1) | |
/** | |
* Demo specific CSS | |
*/ | |
body | |
height 100vh | |
background $red | |
img | |
max-width 100% | |
.demo-btns | |
header | |
padding 7vh 10vw | |
background $modal-bg | |
display flex | |
align-items center | |
h1 | |
margin 0 | |
color rgba(black, 0.54) | |
font-weight 300 | |
.info | |
background $red | |
padding 3vh 10vw | |
height 70vh | |
display flex | |
align-items center | |
justify-content center | |
flex-flow column wrap | |
p | |
text-align: center | |
color white | |
.link | |
font-size 20px | |
.modal__trigger | |
margin-right 3px | |
@media (max-width 640px) | |
margin-bottom ($space / 3) | |
.demo-close | |
position absolute | |
top 0 | |
right 0 | |
margin: ($space / 2) | |
padding: ($space / 4) | |
background rgba(black, 0.3) | |
border-radius 50% | |
$ease() | |
svg | |
width 24px | |
fill: white | |
pointer-events none | |
vertical-align top | |
&:hover | |
background rgba(black, 0.6); | |
.logo | |
position: fixed | |
bottom: 3vh | |
right: 3vw | |
z-index: 2 | |
img | |
width: 45px | |
transform: rotate(0) | |
$ease() | |
&:hover | |
transform: rotate(180deg) scale(1.1) |