Skip to content

How to make a one-pager navigation with waypoints to enable/disable nav elements?

I’m stuck at my one-pager navigation logic. As you can see in my detailed example below, I already have a dynamic navigation depending on the scroll position. Also, when you click a navigation item, you are getting scrolled down to the specific section.

What I’m stuck at is the point to add/remove the active class to the navigation item. Somehow, I can’t find a logic in my brain to make this a smart part of my navigation. I know that I can make some static JS code, but that’s not what I want.

At least, I just want to add another menu item and an element containing the data-waypoint attribute.

It’s not an option to add this to the item click functionality, since the user can scroll down manually, which then don’t get recognized!

(function($) {
  $(document).ready(function() {
    let nav = $('#nav');
    let navOffsetTop = nav.offset().top;

    $(window).scroll(function() {
      if ($(this).scrollTop() >= navOffsetTop) {
        nav.addClass('sticky');
      } else {
        nav.removeClass('sticky');
      }

      // Somehow add logic here for changing active classes...
      if ($(this).scrollTop() >= $('#content').offset().top) {
        //console.log('Reached');
      }
    });

    $('.nav-item').click(function() {
      let navPosition = nav.css('position');
      let navOuterHeight = nav.outerHeight();
      let scrollTo = $(this).data('scroll-to');
      let scrollTop = $(`[data-waypoint=${scrollTo}]`).offset().top - navOuterHeight - 15;

      if (navPosition === 'static') {
        scrollTop = scrollTop - navOuterHeight - 30;
      }

      $([document.documentElement, document.body]).animate({
        scrollTop: scrollTop
      }, 500);
    });
  });
})(jQuery);
#page {
  width: 100%;
  padding-left: 50px;
  padding-right: 50px;
  box-sizing: border-box;
  border: 1px solid;
}

#container {
  width: 100%;
  height: 1500px;
}

#nav {
  width: 100%;
  background-color: #aaa;
  margin-bottom: 30px;
  z-index: 1;
  display: flex;
  justify-content: space-between;
}

#nav .nav-item {
  padding: 20px;
  display: flex;
  justify-content: center;
  flex: 1;
  cursor: pointer;
}

#nav .nav-item.active {
  color: #fff;
}

#nav.sticky {
  position: fixed;
  top: 0;
  left: 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="page">
  <h1>Lorem Impsum page</h1>
  <div id="nav">
    <div class="nav-item active" data-scroll-to="home">Home</div>
    <div class="nav-item" data-scroll-to="content">Content</div>
    <div class="nav-item" data-scroll-to="about">About</div>
  </div>
  <div id="container">
    <div id="home" data-waypoint="home">
      <h2>Home</h2>
      <div class="inner">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
        takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores
        et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</div>
    </div>
    <div id="content" data-waypoint="content">
      <h2>Content</h2>
      <div class="inner">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
        takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores
        et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</div>
    </div>
    <div id="about" data-waypoint="about">
      <h2>About</h2>
      <div class="inner">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
        takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores
        et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</div>
    </div>
  </div>
</div>

Answer

I’m happy to share my answer I’ve found. I was first looking for a simple solution, but ended up using Waypoints jQuery library.

This is my solution. I hope it helps someone:

(function($) {
  $(document).ready(function() {
    let nav = $('#nav');
    let navOffsetTop = nav.offset().top;
    let navItem = $('.nav-item', nav);

    $(window).on('resize, scroll', function() {
      initNav(nav, navOffsetTop);
      initWaypoints(nav, navItem);
    });

    $(window).resize(function() {
      setNavHeight(nav);
    });

    navItem.click(function() {
      let navOuterHeight = nav.outerHeight();
      let scrollTo = $(this).data('scroll-to');
      let scrollTop = $(`[data-waypoint=${scrollTo}]`).offset().top - navOuterHeight - 10;

      $([document.documentElement, document.body]).animate({
        scrollTop: scrollTop
      }, 500);
    });

    setNavHeight(nav);
    initNav(nav);
  });

  function setNavHeight(nav) {
    let navItems = $('#nav-items', nav);

    nav.css('min-height', `${navItems.outerHeight()}px`);
  }

  function initNav(nav, navOffsetTop) {
    let navItems = $('#nav-items', nav);
    let windowScrollTop = $(window).scrollTop();

    if (windowScrollTop >= navOffsetTop) {
      navItems.addClass('sticky');
    } else {
      navItems.removeClass('sticky');
    }
  }

  function initWaypoints(nav, navItem) {
    $('.block-waypoint').waypoint(function() {
      let waypoint = $(this.element).data('waypoint');
      let navItemWaypoint = $(`.nav-item[data-scroll-to=${waypoint}]`, nav);

      navItem.removeClass('is-active');

      if (navItemWaypoint.length) {
        navItemWaypoint.addClass('is-active');
      }
    }, {
      offset: 69
    });
  }
})(jQuery);
#page {
  width: 100%;
  padding-left: 50px;
  padding-right: 50px;
  box-sizing: border-box;
  border: 1px solid;
}

#container {
  width: 100%;
  height: 1500px;
}

#nav {
  margin-bottom: 30px;
}

#nav-items {
  width: 100%;
  background-color: #aaa;
  margin-bottom: 30px;
  z-index: 1;
  display: flex;
  justify-content: space-between;
}

#nav-items.sticky {
  position: fixed;
  top: 0;
  left: 0;
}

.nav-item {
  padding: 20px;
  display: flex;
  justify-content: center;
  flex: 1;
  cursor: pointer;
}

.nav-item.is-active {
  color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/waypoints/4.0.1/jquery.waypoints.min.js"></script>
<div id="page">
  <h1>Lorem Impsum page</h1>
  <div id="nav">
    <div id="nav-items">
      <div class="nav-item is-active" data-scroll-to="home">Home</div>
      <div class="nav-item" data-scroll-to="content">Content</div>
      <div class="nav-item" data-scroll-to="about">About</div>
    </div>
  </div>
  <div id="container">
    <div id="home" class="block-waypoint" data-waypoint="home">
      <h2>Home</h2>
      <div class="inner">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
        takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores
        et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</div>
    </div>
    <div id="content" class="block-waypoint" data-waypoint="content">
      <h2>Content</h2>
      <div class="inner">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
        takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores
        et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</div>
    </div>
    <div id="about" class="block-waypoint" data-waypoint="about">
      <h2>About</h2>
      <div class="inner">Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea
        takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores
        et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
        At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.</div>
    </div>
  </div>
</div>

Also, I’ve wrapped the nav items into a wrapper and set the margin to the outer nav. During page init and resize, I’m setting a min-width to the outer nav to prevent any content jumping and issues with the waypoints. Otherwise, the offset and all the other calculation stuff makes a lot of problems.