0

I am trying to combine code from @David, with code from @Luisdanielroviracontreras to make a cool index menu for my portfolio case studies. So far I have been able to animate the item background when clicked: https://codepen.io/andyradall/pen/BaKJpbM

But as you can see, I run into problems when trying to animate the background of the menu item based on scroll position. The background item ends up in the wrong place: https://codepen.io/andyradall/pen/WNwdRLq

Note. My problem:

  1. specificity and selecting the right HTML elements in the js.
  2. Attaching the active class to the button rather than just the a
  3. Alternatively I need to remove the button and attach the .effect to the a

Here is the current code for my hightlight navigation section:

// Header Sticky
window.onscroll = function() {myFunction()};

var header = document.getElementById("navigation");
var sticky = header.offsetTop;

function myFunction() {
  if (window.pageYOffset > sticky) {
    header.classList.add("sticky");
  } else {
    header.classList.remove("sticky");
  }
}

/* Header buttons Luis that attatches active to buttons on click. */
/*
const buttons = document.querySelectorAll('.header-navbar button')
const effect = document.querySelector('.effect')

buttons.forEach((item) => {
  item.addEventListener('click', (evt) => {
      const x = evt.target.offsetLeft
      buttons.forEach((btn) => { btn.classList.remove('active') })
      evt.target.classList.add('active')
      anime({
          targets: '.effect',
          left: `${x}px`,
          duration: 600,
      })
  })
})
*/

/* header buttons on scroll from @Daniel at stackoverflow that misses the buttons and only attatches the active to the link*/

// caches the navigation links
var $navigationLinks = $('#navigation > ul > li > a');

// what I really want:
/*var buttons = document.querySelectorAll('.header-navbar button')
var effect = document.querySelector('.effect')*/

// cache (in reversed order) the sections
var $sections = $($(".section").get().reverse());

// map each section id to their corresponding navigation link
// here i really want to map each section id to the corresponding button
var sectionIdTonavigationLink = {};
$sections.each(function() {
    var id = $(this).attr('id');
    sectionIdTonavigationLink[id] = $('#navigation > ul > li > a[href=\\#' + id + ']');
});

// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
    var lastCall, timeoutId;
    return function () {
        var now = new Date().getTime();
        if (lastCall && now < (lastCall + interval) ) {
            // if we are inside the interval we wait
            clearTimeout(timeoutId);
            timeoutId = setTimeout(function () {
                lastCall = now;
                fn.call();
            }, interval - (now - lastCall) );
        } else {
            // otherwise, we directly call the function 
            lastCall = now;
            fn.call();
        }
    };
}

function highlightNavigation() {
    // get the current vertical position of the scroll bar
    var scrollPosition = $(window).scrollTop();
    const effect = document.querySelector('.effect')
   
    // iterate the sections
    $sections.each(function() {
        var currentSection = $(this);
        // get the position of the section
        var sectionTop = currentSection.offset().top;
        var x = currentSection.offset().top;
        console.log(x);
        // if the user has scrolled over the top of the section  
        if (scrollPosition >= sectionTop) {
            // get the section id
            var id = currentSection.attr('id');
            // get the corresponding navigation link
            var $navigationLink = sectionIdTonavigationLink[id];
            // if the link is not active
            if ($navigationLink.hasClass('active')) {
                // remove .active class from all the links
                $navigationLinks.removeClass('active');
                // add .active class to the current link
                $navigationLink.addClass('active')
               anime({
                targets: '.effect',
                left: `${x}px`,
                duration: 600,
               });
            }
            // we have found our section, so we return false to exit the each loop
            return false;
        }
    });
}

$(window).scroll( throttle(highlightNavigation,100) );

// if you don't want to throttle the function use this instead:
//$(window).scroll( highlightNavigation );
/* Global */

* {
  list-style: none;
  outline: none;
  padding: 0;
  margin: 0;
  font-family: 'Poppins', sans-serif;
  box-sizing: border-box;
}

body {
  --primary: 25,91,255;
  --color: 44, 62, 80;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: #f4f7f8;
 /* height: calc(var(--vh, 1vh) * 100); */
 /* overflow: hidden; */
  color: rgb(var(--color));
  width: 100%;
}

main {
  width: 100%;
}

/* .sticky attatch to #navigation on scroll past #navigation */
.sticky {
  position: fixed;
  top: 0;
}

.header-navbar ul {
list-style: none;
display: flex;
align-items: center;
justify-content: space-evenly;
width: 100%;
background: #fff;
}

.header-navbar {
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  width: 100%;
  background: #fff;
  padding: 24px 8px 24px 8px;
  box-shadow: 0px 0px 30px 0px rgba(0,0,0,.05);
}

.header-navbar button {
  width: 140px;
  height: 60px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0px;
  background: transparent;
  border-radius: 20px;
  transition: all .25s ease;
}

/*.header-navbar a {
  width: 160px;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0px;
  background: transparent;
  border-radius: 20px;
  transition: all .25s ease;
}*/

.header-navbar a{
  /*color: #333; */
  font-size: 1rem;
  padding: 10px 10px 10px 10px;
  text-decoration: none;
}

/* to use if adding back link*/
.header-navbar a:active:not(.float) {
  transform: scale(1.2);
}

.header-navbar button.active {
  color: rgb(232, 76, 79);
}

/* if icon not text */
.header-navbar button i {
  font-size: 1.2rem;
  pointer-events: none;
}

/*background effect*/

.con-effect {
  position: absolute;
  width: 100%;
  height: 100%;
  /*top: 0px;
  left: 0px;*/
  overflow: hidden;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

.effect {
  background: rgba(232, 76, 232, 0.15);
  width: 160px;
  height: 70px;
  position: absolute;
  left: 100px;
  border-radius: 20px;
}

/* Content sections styling */

.hero {
  width:100%;
  padding: 32px;
}

.section{
width:100%;
height: 500px;
padding: 32px;
}

#first {
  background-color: #8face0;
}

#second {
  background-color: #a388e8;
}

#third {
  background-color: #f4769e;
}

#fourth {
  background-color: #8face0;
}

#fifth {
  background-color: #a388e8;
}

#bottom-spacer {
  height: 2200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src=https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.0/anime.min.js></script>
<body>
    <main>
        <section id="intro" class="hero">
            <div>
              <h1>This will be the hero section</h1>
            </div>
        </section>    
        <div id="navigation" class="header-navbar">
            <div class="con-effect">
            <div class="effect"></div>
            </div>

            <ul>
            <li><a href="#intro"><button>Introduction</button></a></li>
            <li><a href="#first"><button>1. User research</button></a></li>
            <li><a href="#second"><button>2. Ideation</button></a></li>
            <li><a href="#third"><button>3. Design phase</button></a></li>
            <li><a href="#fourth"><button>4. Testing</button></a></li>
            <li><a href="#fifth"><button>5. Results</button></a></li>
        </ul>    
</div>

        </div>  
               <section class="sections">
                   <section id="first" class="section">
                       <h2>1. User research</h2>
                   </section>    
                   <section id="second" class="section">
                       <h2>2. Ideate</h2>
                   </section>  
                   <section id="third" class="section">
                       <h2>3. Design phase</h2>
                   </section>
                   <section id="fourth" class="section">
                       <h2>4. Testing</h2>
                   </section>
                   <section id="fifth" class="section">
                       <h2>5. Results</h2>
                    </section>                    
                    <section id="bottom-spacer">
                     </section>   
           </section>
</main>
   
</body>
  • it seems I need to add the buttons back, and figure out how to add the active class to the button? Now it seems the active class is only added to the link? – Anders Rå Sep 06 '20 at 19:07

1 Answers1

0

Finally solve it! Solution:

  1. Find the active navigationlink offset left, store it in var x
  2. Use navigationlink offset left value (x) to place the background effect

Now, there is only styling and finetuning left :) Final will receive updates here: https://codepen.io/andyradall/pen/WNwdRLq

// sticky header
window.onscroll = function() {myFunction()};

var header = document.getElementById("navigation");
//var effectClass = document.getElementById("effectClass")
var sticky = header.offsetTop;

function myFunction() {
  if (window.pageYOffset > sticky) {
    header.classList.add("sticky");
    //effectClass.classList.add("effect")
  } else {
    header.classList.remove("sticky");
    //effectClass.classList.remove("effect")
  }
}


// cache the navigation links 
var $navigationLinks = $('#navigation > ul > li > a');
// cache (in reversed order) the sections
var $sections = $($(".section").get().reverse());

// map each section id to their corresponding navigation link
var sectionIdTonavigationLink = {};
$sections.each(function() {
    var id = $(this).attr('id');
    sectionIdTonavigationLink[id] = $('#navigation > ul > li > a[href=\\#' + id + ']');
});

// throttle function, enforces a minimum time interval
function throttle(fn, interval) {
    var lastCall, timeoutId;
    return function () {
        var now = new Date().getTime();
        if (lastCall && now < (lastCall + interval) ) {
            // if we are inside the interval we wait
            clearTimeout(timeoutId);
            timeoutId = setTimeout(function () {
                lastCall = now;
                fn.call();
            }, interval - (now - lastCall) );
        } else {
            // otherwise, we directly call the function 
            lastCall = now;
            fn.call();
        }
    };
}

function highlightNavigation() {
    // get the current vertical position of the scroll bar
    var scrollPosition = $(window).scrollTop();
    const effect = document.querySelector('.effect')

    // iterate the sections
    $sections.each(function() {
        var currentSection = $(this);
        // get the position of the section
        var sectionTop = currentSection.offset().top;
        // if the user has scrolled over the top of the section  
        if (scrollPosition >= sectionTop) {
            // get the section id
            var id = currentSection.attr('id');
            // get the corresponding navigation link
            var $navigationLink = sectionIdTonavigationLink[id];
            // if the link is not active
            if (!$navigationLink.hasClass('active')) {
                // remove .active class from all the links
                $navigationLinks.removeClass('active');
                // add .active class to the current link
                $navigationLink.addClass('active');
                var x = $navigationLink.offset().left;
                anime({
                    targets: '.effect',
                    left: `${x-28}px`,
                    duration: 600,
                    endDelay: 1000,
                })
            }
            // we have found our section, so we return false to exit the each loop
            return false;
        }
    });
}

 $(window).scroll( throttle(highlightNavigation,100) );

// if you don't want to throttle the function use this instead:
// $(window).scroll( highlightNavigation );
* {
  list-style: none;
  outline: none;
  padding: 0;
  margin: 0;
  font-family: 'Poppins', sans-serif;
  box-sizing: border-box;
}

body {
  --primary: 25,91,255;
  --color: 44, 62, 80;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  background: #f4f7f8;
 /* height: calc(var(--vh, 1vh) * 100); */
 /* overflow: hidden; */
  color: rgb(var(--color));
  width: 100%;
}

main {
  width: 100%;
}

/* .sticky attatch to #navigation on scroll past #navigation */
.sticky {
  position: fixed;
  top: 0;
}

.header-navbar ul {
list-style: none;
display: flex;
align-items: center;
justify-content: space-evenly;
width: 100%;
background: #fafafa;
}

.header-navbar {
  display: flex;
  align-items: center;
  justify-content: space-evenly;
  width: 100%;
  background: #fafafa;
  padding: 2px 0 2px 0;
  box-shadow: 0 1px 2px rgba(0,0,0,0.06), 
  0 2px 4px rgba(0,0,0,0.06), 
  0 4px 8px rgba(0,0,0,0.06), 
  0 8px 16px rgba(0,0,0,0.06),
  0 16px 32px rgba(0,0,0,0.06), 
  0 32px 64px rgba(0,0,0,0.06);
}

.header-navbar li {
  width: 160px;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0px;
  background: transparent;
  border-radius: 20px;
  transition: all .25s ease;
}

/*.header-navbar a {
  width: 160px;
  height: 50px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0px;
  background: transparent;
  border-radius: 20px;
  transition: all .25s ease;
}*/

.header-navbar a{
  color: #333;
  font-size: 1rem;
  /*padding: 8px 8px 8px 8px;*/
  text-decoration: none;
}

/* :not excludes class for specific use */
.header-navbar a:active:not(.float) {
  transform: scale(1.2);
}

.active {
  
  color: rgb(232, 76, 79)!important;
}

.header-navbar a:active {
 
  color: rgb(232, 76, 79)!important;
}

/* if icon not text */
.header-navbar a i {
  font-size: 1.2rem;
  pointer-events: none;
}

/*background effect*/

.con-effect {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0px;
  left: 0px;
  overflow: hidden;
  pointer-events: none;
  display: flex;
  align-items: center;
  justify-content: center;
}

.effect {
  background: rgba(232, 76, 232, 0.15);
  width: 178px;
  height: 40px;
  position: absolute;
  left: 1px;
  border-radius: 40px;
}

/* Content sections styling */

.hero {
  width:100%;
  padding: 32px;
}

.section{
  color: #666;
width:100%;
/*height: 500px;*/
padding: 32px;
}

#first {
  background-color: #8face0;
}

#second {
  background-color: #a388e8;
}

#third {
  background-color: #f4769e;
}

#fourth {
  background-color: #8face0;
}

#fifth {
  background-color: #a388e8;
}

#bottom-spacer {
  height: 2200px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/animejs/3.2.0/anime.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<body>
     <main>
        <section id="intro" class="hero section">
            <div>
              <h1>This will be the hero section</h1>
            </div>
        </section>    
        <div id="navigation" class="header-navbar">
            <div class="con-effect">
            <div id="effectClass" class="effect"></div>
            </div>
        <ul>
            <li><a href="#intro">Introduction</a></li>
            <li><a href="#first">1. User research</a></li>
            <li><a href="#second">2. Ideation</a></li>
            <li><a href="#third">3. Design phase</a></li>
            <li><a href="#fourth">4. Testing</a></li>
            <li><a href="#fifth">5. Results</a></li>
        </ul> 
        </div>  
               <section class="sections">
                   <section id="first" class="section">
                       <h2>1. User research</h2>
                   </section>    
                   <section id="second" class="section">
                       <h2>2. Ideate</h2>
                   </section>  
                   <section id="third" class="section">
                       <h2>3. Design phase</h2>
                   </section>
                   <section id="fourth" class="section">
                       <h2>4. Testing</h2>
                   </section>
                   <section id="fifth" class="section">
                       <h2>5. Results</h2>
                    </section>                    
                    <section id="bottom-spacer">
                     </section>   
           </section>
</main>
</body>