photo Navbar Controller

Controller for Bootstrap navbar


Javascript

class NavbarController {
  constructor(options) {
    this.prevScrollPos = window.scrollY;
    this.navbar = document.querySelector('.navbar');
    this.collapse = document.querySelector('.navbar-toggler');
    this.mobileMenu = document.querySelector('.navbar-collapse');
    this.navbarLogo = document.querySelector('.navbar-logo');
    this.scrollThreshold = options.scrollThreshold || 80;
    this.backToTopThreshold = options.backToTopThreshold || 400;
    this.backToTopHide = options.backToTopHide || 'right: -150px;';
    this.backToTopShow = options.backToTopShow || 'right: 36px;';
    this.navbarBackground = options.navbarBackground || 'bg-body';
    this.toggleShadow = options.toggleShadow || true;
    this.behavior = this.getBehavior();
    this.backToTopSelector = options.backToTopSelector || '.back-to-top';
    this.backToTop = document.querySelector(this.backToTopSelector);
    this.attachEvents();
  }
  getBehavior() {
    if (this.navbar) {
      if (this.navbar.classList.contains('slide-up')) {
        return 'slideUp';
      } else if (this.navbar.classList.contains('hidden')) {
        return 'hideDown';
      } else if (this.navbar.classList.contains('bg-transparent')) {
        return 'transparent';
      } else if (this.navbar.classList.contains('none')) {
        return 'none';
      } else {
        return 'shrink';
      }
    } else {
      return 'transparent';
    }
  }
  attachEvents() {
    window.addEventListener('scroll', () => {
      this.handleScroll();
    });

    document.querySelectorAll('a[href^="#"]').forEach((anchor) => {
      anchor.addEventListener('click', (e) => {
        e.preventDefault();
        this.smoothScrollToAnchor(anchor.getAttribute('href'));
      });
    });

    if (this.backToTop) {
      this.backToTop.addEventListener('click', (e) => {
        e.preventDefault();
        this.scrollToTop();
      });
    }
  }

  handleScroll() {
    const currentScrollPos = window.scrollY;
    if (currentScrollPos > this.backToTopThreshold) {
      this.backToTop.style = this.backToTopShow;
    } else {
      this.backToTop.style = this.backToTopHide;
    }

    if (this.navbar) {
      switch (this.behavior) {
        case 'hideDown':
          this.hideDownShowUp(currentScrollPos);
          break;
        case 'slideUp':
          this.slideUp(currentScrollPos);
          break;
        case 'transparent':
          this.transparent(currentScrollPos);
          break;
        case 'shrink':
          this.shrink(currentScrollPos);
          break;
        default:
          break;
      }
    }
    this.prevScrollPos = currentScrollPos;
  }

  smoothScrollToAnchor(anchorLink) {
    if (anchorLink === '#') return;
    const anchorID = document.querySelector(anchorLink);
    if (!anchorID) {
      console.error(`debug: ${anchorLink} does not exist on the page`);
      return;
    }
    let offset = this.navbar.offsetHeight;
    if (
      this.navbar.offsetTop === 0 &&
      this.mobileMenu.classList.contains('show')
    ) {
      offset -= this.mobileMenu.offsetHeight;
    }
    window.scrollTo({
      top: anchorID.offsetTop - offset,
      behavior: 'smooth',
    });
    this.collapse.click();
  }

  scrollToTop() {
    window.scrollTo({ top: 0, behavior: 'smooth' });
  }

  hideDownShowUp(currentScrollPos) {
    this.navbar.classList.toggle(
      'hidden',
      this.prevScrollPos < currentScrollPos
    );
  }

  slideUp(currentScrollPos) {
    const shouldShow = currentScrollPos > this.scrollThreshold;
    this.navbar.classList.toggle('slide-up', !shouldShow);
    this.navbar.classList.toggle('slide-down', shouldShow);
  }

  transparent(currentScrollPos) {
    const shouldTransparent = currentScrollPos > this.scrollThreshold;
    this.navbar.classList.toggle('bg-transparent', !shouldTransparent);
    this.navbar.classList.toggle(this.navbarBackground, shouldTransparent);
    if (this.toggleShadow) {
      this.navbar.classList.toggle('shadow', shouldTransparent);
    }
  }

  shrink(currentScrollPos) {
    const shouldShrink = currentScrollPos > this.scrollThreshold;
    this.navbarLogo.classList.toggle('shrunk', shouldShrink);
    this.navbar.classList.toggle(this.navbarBackground, shouldShrink);
    if (this.toggleShadow) {
      this.navbar.classList.toggle('shadow', shouldShrink);
    }
  }
}

How to use the class

document.addEventListener('DOMContentLoaded', function () {
  const navbar = document.querySelector('.navbar');
  const logo = document.getElementById('navbarLogo');
  const options = {
    scrollThreshold: 80,
    backToTopThreshold: 400,
    backToTopSelector: '.back-to-top',
    backToTopHide: 'right: -150px;',
    backToTopShow: 'right:36px;',
    navbarBackground: 'bg-info-subtle',
    toggleShadow: true,
  };

  if (navbar) {
    new NavbarController(options);
  }

  if (logo) {
    new NavbarController(options);
  }
});

CSS

main {
  padding-top: 60px;
}

.navbar, .navbar-logo {
  transition: all 820ms ease-in-out;
}

.back-to-top {
  position: fixed;
  top: calc(100vh - 72px);
  bottom: 5px;
  right: -150px;
  transition: all 820ms ease-in-out;
}

@media (min-width: 576px) {
  .hidden {
    opacity: 0;
  }
}

@media (min-width: 576px) {
  .slide-down {
    top: 0;
  }
}

@media (min-width: 576px) {
  .slide-up {
    top: -150px;
  }
}

@media (min-width: 576px) {
  .navbar-logo {
    height: 80px;
    width: 80px;
  }
}

@media (min-width: 576px) {
  .navbar-logo.shrunk {
    height: 40px;
    width: 40px;
  }
}

 

Go back