Walrus
Walrus

Reputation: 21

Navbar scroll into view causing links to fail when clicked

EDIT: PROBLEM SOLVED. I'll keep the post here for helping others who might be facing the same issue. The bug was related to letting the browser handle the clicks on the navbar. Solved by adding an onclick function with preventDefault() to override the click event default reaction, followed by a JS command to scroll the content to the correct section. There is a link to codePen at the end.


I have an online restaurant menu under development. It has a horizontal navbar fixed at the top. The links on the navbar scroll horizontally and the content scrolls vertically. The links point to #sections on the same page. I managed to make the links get highlighted as their corresponding content section scrolls by. I also managed to make the menu items scroll themselves to the center of the screen as the user scrolls the main content. But here is the problem. This last feature causes the scroll to fail when the user clicks a menu link. It seems that this function interrupts the scrolling triggered by the click, so the page only scrolls like 10px and stops. If I remove this auto-centering feature of the menu items, the click works back again. I've tried to force the page scroll by attaching an onclick function to the menu links, but it still didn't work. Below is the current code explained:

// Get all sections that have an ID defined
const sections = document.querySelectorAll("section[id]");

// Add an event listener listening for scroll
window.addEventListener("scroll", navHighlighter);

function navHighlighter() {

  // Get current scroll position
  let scrollY = window.pageYOffset;

  // Now we loop through sections to get height, top and ID values for each
  sections.forEach(current => {
    const sectionHeight = current.offsetHeight;
    const sectionTop = (current.getBoundingClientRect().top + window.pageYOffset) - 70;
    sectionId = current.getAttribute("id");

    /*
    - If our current scroll position enters the space where current section on screen is, add .active class to corresponding navigation link, else remove it
    - To know which link needs an active class, we use sectionId variable we are getting while looping through sections as an selector
    */
    if (scrollY > sectionTop && scrollY <= sectionTop + sectionHeight) {
      theone = document.querySelector('a.menu_item[href="#' + sectionId + '"]');
      theone.classList.add('active');
    } else {
      document.querySelector('a.menu_item[href="#' + sectionId + '"]').classList.remove('active');
    }
  });
  navScroll(theone); //this is the line that breaks the scroll started by clicks. EDIT: started working with preventDefault.
}

//brings the menu link to the visible area when user scroll thru content. Can also be replaced with scrollIntoView.
function navScroll(item) {
  const menu = document.getElementById("menu");
  const menu_w = menu.offsetWidth;
  const item_w = item.offsetWidth;
  const item_x = item.offsetLeft;
  menu.scrollLeft = item_x - (menu_w / 2) + (item_w / 2);
}

//EDIT: it is now working since I added preventDefault.
function forceScroll(sectionID) {
  event.preventDefault();
  const section = document.getElementById(sectionID);
  window.scrollTo(0, section.offsetTop);
}
.content {
  height: 500px;
  border-bottom: 1px solid black;
}

.scroll {
  white-space: nowrap;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  -ms-overflow-style: -ms-autohiding-scrollbar;
  scroll-behavior: smooth;
}

.scroll::-webkit-scrollbar {
  display: none;
}

div#menu {
  position: fixed;
  top: 0px;
  width: 100%;
  height: 50px;
  z-index: 999;
  text-align: center;
}

a.menu_item {
  display: inline-block;
  vertical-align: middle;
  text-align: center;
  height: 50px;
  background-color: #fff;
  color: #000;
}

a.menu_item.active {
  background-color: #000;
  color: #fff;
}

section {
  display: block;
}
<div id="menu" class="scroll">
  <a class="menu_item" href="#sec_1" onclick="forceScroll('sec_1')">Link 1</a>
  <a class="menu_item" href="#sec_2" onclick="forceScroll('sec_2')">Link 2</a>
  <a class="menu_item" href="#sec_3" onclick="forceScroll('sec_3')">Link 3</a>
</div>
<section id="sec_1">
  <div class="content">Some content</div>
</section>
<section id="sec_2">
  <div class="content">Some content</div>
</section>
<section id="sec_3">
  <div class="content">Some content</div>
</section>

So basically, if I do not use the navScroll() function, everything works fine. The .active class will be applied to menu itens accordingly as the content scrolls by, be it the user scrolling or by clicking a link in the menu, which makes the content scroll to corresponding section. But when I add the navScroll function, to make the menu item scroll into view, things only work if the user scrolls the content directly. When clicking a link, the scroll is quickly interrupted and nothing happens.

I appreciate if you keep it Javascript only, but I'll accept jQuery if it comes to be the only way to solve this.

UPDATE 1: the problem seems to be occurring only in Chrome (version 112.0.5615.49). I'm on a Mac and haven't tested on a PC. Safari also presents a few minor bugs like not scrolling smoothly and not applying the top offset so section titles don't get behind the navbar. But those are not the main issue here.

UPDATE 2: I was able to reproduce the error in codePen. The problem is related to the smooth scroll behavior. Without it, the scroll works fine. With it, the scroll only works if I remove the navScroll() call, which makes the navbar links scroll into view. Seems like when a second smooth scroll is started, the current one is interrupted.

Here is the codepen link: https://codepen.io/pen/GRYgxda

UPDATE 3: I managed to make everything work now by adding onclick='forceScroll([section_id])' to the links. The event.preventDefault() line will stop browser default action when clicking the links, and then it will make the window scroll to the correct section via JS command window.scrollTo().

Upvotes: 0

Views: 705

Answers (0)

Related Questions