Multi-level Responsive Menu In Vanilla JavaScript – Last Navigation

Category: Javascript , Menu & Navigation | November 11, 2017
Author: Michal Niewitala
Views Total: 75
Official Page: Go to website
Last Update: November 11, 2017
License: MIT

Preview:

Multi-level Responsive Menu In Vanilla JavaScript – Last Navigation

Description:

Last Navigation is a vanilla JavaScript responsive navigation system which transforms the regular horizontal menu into a togglable fullscreen menu on mobile devices.

How to use it:

Create the header navigation with nested menu items.

<header id="header">
  <nav class="nav container" id="nav">
    <ul class="menu">
      <li class="menu-item"><a href="">Home</a>
      </li>
      <li class="menu-item"><a href="">Templates</a>
        <ul class="sub-menu">
          <li><a href="">Gallery</a>
            <ul class="sub-menu">
              <li><a href="">One</a></li>
              <li><a href="">Wwo</a></li>
            </ul>
          </li>
          <li><a href="">Post</a>
          </li>
        </ul>
      </li>
      <li class="menu-item"><a href="">About</a>
      </li>
      <li class="menu-item"><a href="">Portfolio</a>
        <ul class="sub-menu">
          <li><a href="">Best_project</a>
          </li>
          <li><a href="">Second</a>
          </li>
          <li><a href="">Last</a>
            <ul class="sub-menu">
              <li><a href="">Showcase</a></li>
            </ul>
          </li>
        </ul>
      </li>
      <li class="menu-item"><a href="">Contact</a>
      </li>
    </ul>
  </nav>
</header>

The main JavaScript.

(function() {
  var block, i, j, len, len1, ref, ref1, responsiveMenu, slideToggler, trigger,
    bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

  slideToggler = (function() {
    function slideToggler(el1) {
      this.el = el1;
      this.toggle = bind(this.toggle, this);
      this.getHeight = bind(this.getHeight, this);
      if (!this.el) {
        return;
      }
      window.addEventListener('resize', this.getHeight);
    }

    slideToggler.prototype.getHeight = function() {
      var clone;
      clone = this.el.cloneNode(true);
      clone.style.cssText = 'visibility: hidden; display: block; margin: -999px 0';
      this.height = (this.el.parentNode.appendChild(clone)).clientHeight;
      this.el.parentNode.removeChild(clone);
      return this.height;
    };

    slideToggler.prototype.toggle = function(time) {
      var currHeight, disp, el, end, init, ref, repeat, start;
      this.getHeight();
      time || (time = this.height / 3 + 150);
      currHeight = this.el.clientHeight * (getComputedStyle(this.el).display !== 'none');
      ref = currHeight > this.height / 2 ? [this.height, 0] : [0, this.height], start = ref[0], end = ref[1];
      disp = end - start;
      el = this.el;
      this.el.classList[end === 0 ? 'remove' : 'add']('open');
      this.el.style.cssText = "overflow: hidden; display: block;";
      init = (new Date).getTime();
      repeat = function() {
        var i, instance, ref1, repeatLoop, results, step;
        instance = (new Date).getTime() - init;
        step = start + disp * instance / time;
        if (instance <= time) {
          el.style.height = step + 'px';
        } else {
          el.style.cssText = "display: " + (end === 0 ? 'none' : 'block');
        }
        repeatLoop = requestAnimationFrame(repeat);
        if (ref1 = Math.floor(step), indexOf.call((function() {
          results = [];
          for (var i = start; start <= end ? i <= end : i >= end; start <= end ? i++ : i--){ results.push(i); }
          return results;
        }).apply(this), ref1) < 0) {
          return cancelAnimationFrame(repeatLoop);
        }
      };
      return repeat();
    };

    return slideToggler;

  })();

  ref = document.querySelectorAll('.block');
  for (i = 0, len = ref.length; i < len; i++) {
    block = ref[i];
    block.toggler = new slideToggler(block);
  }

  ref1 = document.querySelectorAll('button');
  for (j = 0, len1 = ref1.length; j < len1; j++) {
    trigger = ref1[j];
    trigger.addEventListener('click', function() {
      var ref2;
      return (ref2 = this.parentNode.querySelector('.block').toggler) != null ? ref2.toggle() : void 0;
    });
  }

  responsiveMenu = (function() {
    function responsiveMenu(nav, opt) {
      var base, k, l, len2, len3, len4, m, menu, menuToggle, ref2, ref3, ref4, sub, subMenu, subMenuToggle;
      this.opt = opt;
      this.breaking = bind(this.breaking, this);
      this.createToggle = bind(this.createToggle, this);
      (base = this.opt).breaking || (base.breaking = '640px');
      this.opt.maxBreaking = parseInt(this.opt.maxBreaking) || 1040;
      menu = nav.querySelector('.menu');
      if (!slideToggler) {
        slideToggler = (function() {
          function slideToggler(el1) {
            this.el = el1;
            this.toggle = bind(this.toggle, this);
            if (!this.el) {
              return;
            }
          }

          slideToggler.prototype.toggle = function() {
            this.el.classList.toggle('open');
            return this.el.style.cssText = "display: " + (this.el.classList.contains('open') ? 'block' : 'none');
          };

          return slideToggler;

        })();
      }
      ref2 = menu.querySelectorAll('ul');
      for (k = 0, len2 = ref2.length; k < len2; k++) {
        sub = ref2[k];
        sub.toggler = new slideToggler(sub);
      }
      menuToggle = this.createToggle(nav, 'menu-toggle', 'Menu');
      menuToggle.addEventListener('click', function() {
        document.documentElement.classList.toggle('nav-open');
        return menuToggle.menu.classList.toggle('open', document.documentElement.classList.contains('nav-open'));
      });
      ref3 = menu.querySelectorAll('ul');
      for (l = 0, len3 = ref3.length; l < len3; l++) {
        subMenu = ref3[l];
        subMenu.parentNode.classList.add('has-children');
        subMenuToggle = this.createToggle(subMenu, 'sub-menu-toggle', '+');
        subMenuToggle.addEventListener('click', function() {
          var len4, m, open, ref4, results;
          this.menu.toggler.toggle();
          ref4 = this.parentNode.parentNode.querySelectorAll('ul.open');
          results = [];
          for (m = 0, len4 = ref4.length; m < len4; m++) {
            open = ref4[m];
            if (open !== this.menu) {
              results.push(open.toggler.toggle());
            } else {
              results.push(void 0);
            }
          }
          return results;
        });
      }
      ref4 = menu.querySelectorAll('.has-children[class*=current] > ul');
      for (m = 0, len4 = ref4.length; m < len4; m++) {
        sub = ref4[m];
        sub.classList.add('open');
      }
      document.addEventListener('DOMContentLoaded', this.breaking);
      window.addEventListener('resize', this.breaking);
      setTimeout(this.breaking, 3000);
    }

    responsiveMenu.prototype.createToggle = function(menu, klass, label) {
      var toggle;
      toggle = menu.parentNode.querySelector("." + klass) || document.createElement("button");
      toggle.classList.add(klass);
      toggle.appendChild(document.createTextNode(label));
      toggle.menu = menu.nodeName === 'UL' ? menu : menu.querySelector('.menu');
      return menu.parentNode.insertBefore(toggle, menu.nextSibling);
    };

    responsiveMenu.prototype.breaking = function() {
      var div, el, isMobile, k, len2, ref2, windowWidth;
      document.body.classList.remove('menu-mobile');
      document.body.classList.add('menu-desktop');
      div = document.createElement('div');
      div.style.cssText = "position: absolute; width: " + this.opt.breaking;
      document.body.appendChild(div);
      this.menuBreak = div.clientWidth || 0;
      document.body.removeChild(div);
      ref2 = document.querySelectorAll("" + this.opt.breaking);
      for (k = 0, len2 = ref2.length; k < len2; k++) {
        el = ref2[k];
        this.menuBreak += this.getWidth(el);
      }
      isMobile = (windowWidth = document.body.clientWidth) <= Math.min(this.menuBreak, this.opt.maxBreaking);
      document.body.classList.toggle('menu-mobile', isMobile);
      document.body.classList.toggle('menu-desktop', !isMobile);
      if (!isMobile) {
        return document.documentElement.classList.remove('nav-open');
      }
    };

    responsiveMenu.prototype.getWidth = function(el) {
      var clone, width;
      if (el.clientWidth > 0) {
        return el.clientWidth;
      }
      clone = el.cloneNode(true);
      clone.style.cssText = 'position: absolute; visibility: hidden; display: block;';
      el.parentNode.appendChild(clone);
      width = clone.clientWidth;
      el.parentNode.removeChild(clone);
      return width;
    };

    return responsiveMenu;

  })();

  new responsiveMenu(document.querySelector('#nav'), {
    breaking: '.logo, .tagline, .menu'
  });

}).call(this);