<div class="squad-navbar" data-behaviour="squad-navbar">
    <nav class="squad-navbar__nav-list" data-behavior="squad-navbar--nav-list" aria-label="Current player squad">
        <a href="#" data-nav-to="squad-block-goalkeepers">
            Goalkeepers
        </a>
        <a href="#" data-nav-to="squad-block-defenders">
            Defenders
        </a>
        <a href="#" data-nav-to="squad-block-midfielders">
            Midfielders
        </a>
        <a href="#" data-nav-to="squad-block-forwards">
            Forwards
        </a>
        <a href="#" data-nav-to="squad-block-staff">
            Staff
        </a>
    </nav>
</div>

Squad navbar

  • Same page navigation navbar
  • [EF-512]

Fixed navbar on scroll:

  1. Navbar gets fixed (along side of header) once scroll pasts navbar element.
  2. Use the same logic of navigation initFixedHeader to be in sync otherwise there would be race condition in positioning elements at the top.
  3. TBD - Animate navlinks when scrolled to particular part of the screen.
  4. Scroll performance issues to addressed.
    • off/on scroll when scrollIntoView happens
    • less scroll event fire and more focus on logic.
    • can we combine certain conditions while scroll?
    • how to avoid scroll event not getting bounded in smaller device?
{
  "data": [
    {
      "title": "Goalkeepers",
      "id": "squad-block-goalkeepers",
      "additionalClasses": ""
    },
    {
      "title": "Defenders",
      "id": "squad-block-defenders",
      "additionalClasses": ""
    },
    {
      "title": "Midfielders",
      "id": "squad-block-midfielders",
      "additionalClasses": ""
    },
    {
      "title": "Forwards",
      "id": "squad-block-forwards",
      "additionalClasses": ""
    },
    {
      "title": "Staff",
      "id": "squad-block-staff",
      "additionalClasses": ""
    }
  ]
}
  • Content:
    /* eslint-disable no-param-reassign */
    const MOBILE_BREAKPOINT = 800;
    
    const initFixedNavbar = parentElement => {
      let prevScrollPos = window.pageYOffset;
      let isScrolling = false;
    
      const minScrollDistance = window.innerWidth <= MOBILE_BREAKPOINT ? 10 : 20;
    
      const fixedHeaderElement = document.querySelector('.fixed-header');
    
      const navElement = parentElement.querySelector(
        '[data-behavior="squad-navbar--nav-list"]'
      );
    
      const navLinks = navElement.querySelectorAll('a');
    
      // get the first link and navto block
      const firstNavLink = navLinks[0];
      const firstNavToElement = document.getElementById(firstNavLink.dataset.navTo);
    
      // get the last link and navto block
      const lastNavLink = navLinks[navLinks.length - 1];
      const lastNavToElement = document.getElementById(lastNavLink.dataset.navTo);
    
      const onScroll = () => {
        if (isScrolling) {
          return;
        }
    
        isScrolling = true;
    
        const startPoint =
          firstNavToElement.offsetHeight + firstNavToElement.offsetTop;
    
        const stopPoint =
          lastNavToElement.offsetHeight + lastNavToElement.offsetTop;
    
        setTimeout(() => {
          const currentScrollPos = window.pageYOffset;
    
          if (currentScrollPos > startPoint && currentScrollPos < stopPoint) {
            parentElement.style.position = 'fixed';
    
            // Scrolling down (at least 20px)
            if (currentScrollPos > prevScrollPos + minScrollDistance) {
              // position it to top
              parentElement.style.top = 0;
              // Scroll up (at least 20px)
            } else if (currentScrollPos < prevScrollPos - minScrollDistance) {
              // if header is visible then set its offsetHeight as offset top position of nav element
              parentElement.style.top = `${fixedHeaderElement.offsetHeight - 1}px`;
            }
          } else {
            parentElement.style.position = 'static';
          }
    
          prevScrollPos = currentScrollPos;
          isScrolling = false;
        }, 110);
      };
    
      return onScroll;
    };
    
    const bindEvents = parentElement => {
      const onScroll = initFixedNavbar(parentElement);
    
      const navlistElement = parentElement.querySelector(
        '[data-behavior="squad-navbar--nav-list"]'
      );
    
      const navlistLinks = navlistElement.querySelectorAll('a');
    
      navlistElement.addEventListener('click', event => {
        event.preventDefault();
        // switch off
        window.removeEventListener('scroll', onScroll, { passive: true });
    
        const { target } = event;
        const navToElementId = target.dataset.navTo;
        const navToElement = document.getElementById(navToElementId);
    
        if (navToElement) {
          navlistLinks.forEach(link => {
            link.classList.remove('active');
          });
    
          target.classList.add('active');
    
          navToElement.scrollIntoView({ behavior: 'smooth', block: 'start' });
        }
    
        // switch on
        setTimeout(() => {
          window.addEventListener('scroll', onScroll, { passive: true });
        }, 50);
      });
    
      window.addEventListener('scroll', onScroll, { passive: true });
    };
    
    export default parentElement => {
      // do scroll top before capturing scroll positions.
      window.scroll({
        top: 0,
        left: 0,
        behavior: 'smooth',
      });
      // event bindings
      bindEvents(parentElement);
    };
    
    
  • URL: /components/raw/squad-navbar/squad-navbar.js
  • Filesystem Path: src/library/components/squad-navbar/squad-navbar.js
  • Size: 3.2 KB
  • Content:
    .squad-navbar {
      display: flex;
      justify-content: center;
      width: 100%;
      background-color: $white;
      transition: all 0.4s ease-out 0s;
      left: 0;
      z-index: 9;
    
      &__nav-list {
        display: none;
      }
    
      @media screen and (min-width: $mq-small) {
        &__nav-list {
          display: flex;
          width: 100%;
    
          a {
            @include text-s;
    
            line-height: 4rem;
            text-decoration: none;
            text-transform: uppercase;
            color: $color-interface-light;
            border-bottom: 2px solid $light-blue;
            width: 100%;
            text-align: center;
    
            &:not(:last-child) {
              padding-right: 1rem;
            }
    
            &.active {
              color: var(--brand-primary);
              border-color: var(--brand-primary);
            }
          }
        }
      }
    }
    
    @media screen and (min-width: $mq-medium) {
      .squad-navbar {
        padding: 0 6rem;
      }
    }
    
  • URL: /components/raw/squad-navbar/squad-navbar.scss
  • Filesystem Path: src/library/components/squad-navbar/squad-navbar.scss
  • Size: 868 Bytes
<div class="squad-navbar" data-behaviour="squad-navbar">
  <nav class="squad-navbar__nav-list" data-behavior="squad-navbar--nav-list" aria-label="Current player squad">
    {{#each data}}
      <a href="#" data-nav-to="{{id}}" {{#if additionalClasses}}class="{{additionalClasses}}" {{/if}}>
        {{title}}
      </a>
    {{/each}}
  </nav>
</div>