Lightweight Vanilla JavaScript Modal Window

Category: Javascript , Modal & Popup | June 20, 2020
Author: tomaszbujnowicz
Views Total: 578 views
Official Page: Go to website
Last Update: June 20, 2020
License: MIT

Preview:

Lightweight Vanilla JavaScript Modal Window

Description:

A lightweight, dependency-free, minimal clean looking modal window written in JavaScript and CSS/CSS3.

How to use it:

1. The required HTML structure for the modal window.

<section class="modal" id="modal-name" data-modal-target>
  <div class="modal__overlay" data-modal-close tabindex="-1"></div>
  <div class="modal__wrapper">
    <div class="modal__header">
      <div class="modal__title">
        Header Title
      </div>
      <button class="modal__close" data-modal-close aria-label="Close Modal"></button>
    </div>
    <div class="modal__content">
      <p>
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Rem in aliquid nulla, sed veritatis, officiis ea aut
        natus quas voluptates perferendis ratione modi ab qui omnis cum labore alias eos.
      </p>
    </div>
  </div>
</section>

2. Create a link to toggle the modal window.

<a href="#" data-modal-trigger aria-controls="modal-name" aria-expanded="false">Open modal</a>

3. The CSS rules to style the modal window.

.modal {
  display: none;      
}
.modal__overlay {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  width: 100%;
  z-index: 200;
  opacity: 0;
  
  transition: opacity 0.2s;
  will-change: opacity;
  background-color: #000;

  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  user-select: none;      
}

.modal__header {
  /* Optional */
  padding: 1.5rem;
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid #ddd;
}

.modal__close {
  /* Optional */
  margin: 0;
  padding: 0;
  border: none;
  background-color: transparent;
  cursor: pointer;
  background-image: url("data:image/svg+xml,%0A%3Csvg width='15px' height='16px' viewBox='0 0 15 16' version='1.1' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'%3E%3Cg id='Page-1' stroke='none' stroke-width='1' fill='none' fill-rule='evenodd'%3E%3Cg id='2.-Menu' transform='translate(-15.000000, -13.000000)' stroke='%23000000'%3E%3Cg id='Group' transform='translate(15.000000, 13.521000)'%3E%3Cpath d='M0,0.479000129 L15,14.2971819' id='Path-3'%3E%3C/path%3E%3Cpath d='M0,14.7761821 L15,-1.24344979e-14' id='Path-3'%3E%3C/path%3E%3C/g%3E%3C/g%3E%3C/g%3E%3C/svg%3E");
  width: 15px;
  height: 15px;
}

.modal__wrapper {
  width: 100%;
  z-index: 9999;
  overflow: auto;
  opacity: 0;

  max-width: 540px;
  max-height: 80vh;
  
  transition: transform 0.2s, opacity 0.2s;
  will-change: transform;
  background-color: #fff;
 
  display: flex;
  flex-direction: column; 

  -webkit-transform: translateY(5%);
  transform: translateY(5%);
  
  -webkit-overflow-scrolling: touch; /* enables momentum scrolling in iOS overflow elements */

  /* Optional */
  box-shadow: 0 2px 6px #777;
  border-radius: 5px;    
  margin: 20px;  
}

.modal__content {
  position: relative;
  overflow-x: hidden;
  overflow-y: auto;
  height: 100%;
  flex-grow: 1;
  /* Optional */
  padding: 1.5rem;
}

.modal.is-active {
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  right: 0;
  left: 0;
  bottom: 0;
  z-index: 9999;
}

.modal.is-visible .modal__wrapper {
  opacity: 1;
  -webkit-transform: translateY(0);
  transform: translateY(0);
}

.modal.is-visible .modal__overlay {
  opacity: 0.5;
}

4. The main JavaScript to enable the modal window.

var modal = function () {

  /**
  * Element.closest() polyfill
  * https://developer.mozilla.org/en-US/docs/Web/API/Element/closest#Polyfill
  */
  if (!Element.prototype.closest) {
    if (!Element.prototype.matches) {
      Element.prototype.matches = Element.prototype.msMatchesSelector || Element.prototype.webkitMatchesSelector;
    }
    Element.prototype.closest = function (s) {
      var el = this;
      var ancestor = this;
      if (!document.documentElement.contains(el)) return null;
      do {
        if (ancestor.matches(s)) return ancestor;
        ancestor = ancestor.parentElement;
      } while (ancestor !== null);
      return null;
    };
  }


  //
  // Settings
  //
  var settings = {
    speedOpen: 50,
    speedClose: 250,
    activeClass: 'is-active',
    visibleClass: 'is-visible',
    selectorTarget: '[data-modal-target]',
    selectorTrigger: '[data-modal-trigger]',
    selectorClose: '[data-modal-close]',
  };


  //
  // Methods
  //

  // Toggle accessibility
  var toggleccessibility = function (event) {
    if (event.getAttribute('aria-expanded') === 'true') {
      event.setAttribute('aria-expanded', false);
    } else {
      event.setAttribute('aria-expanded', true);
    }   
  };

  // Open Modal
  var openModal = function (trigger) {

    // Find target
    var target = document.getElementById(trigger.getAttribute('aria-controls'));

    // Make it active
    target.classList.add(settings.activeClass);

    // Make body overflow hidden so it's not scrollable
    document.documentElement.style.overflow = 'hidden';

    // Toggle accessibility
    toggleccessibility(trigger);

    // Make it visible
    setTimeout(function () {
      target.classList.add(settings.visibleClass);
    }, settings.speedOpen); 

  };

  // Close Modal
  var closeModal = function (event) {

    // Find target
    var closestParent = event.closest(settings.selectorTarget),
      childrenTrigger = document.querySelector('[aria-controls="' + closestParent.id + '"');

    // Make it not visible
    closestParent.classList.remove(settings.visibleClass);

    // Remove body overflow hidden
    document.documentElement.style.overflow = '';

    // Toggle accessibility
    toggleccessibility(childrenTrigger);

    // Make it not active
    setTimeout(function () {
      closestParent.classList.remove(settings.activeClass);
    }, settings.speedClose);             

  };

  // Click Handler
  var clickHandler = function (event) {

    // Find elements
    var toggle = event.target,
      open = toggle.closest(settings.selectorTrigger),
      close = toggle.closest(settings.selectorClose);

    // Open modal when the open button is clicked
    if (open) {
      openModal(open);
    }

    // Close modal when the close button (or overlay area) is clicked
    if (close) {
      closeModal(close);
    }

    // Prevent default link behavior
    if (open || close) {
      event.preventDefault();
    }

  };

  // Keydown Handler, handle Escape button
  var keydownHandler = function (event) {

    if (event.key === 'Escape' || event.keyCode === 27) {

      // Find all possible modals
      var modals = document.querySelectorAll(settings.selectorTarget),
        i;

      // Find active modals and close them when escape is clicked
      for (i = 0; i < modals.length; ++i) {
        if (modals[i].classList.contains(settings.activeClass)) {
          closeModal(modals[i]);
        }
      }

    }

  };


  //
  // Inits & Event Listeners
  //
  document.addEventListener('click', clickHandler, false);
  document.addEventListener('keydown', keydownHandler, false);


};

modal();

Changelog:

06/20/2020

  • Add close modal links inside the modal content

You Might Be Interested In:


Leave a Reply