Search code examples
htmlcss

Drop down box wont move with button when button is moved


.CategoryBlock {
  margin-top: 0.5rem;
  gap: 1rem;
  display: flex;
  overflow-x: scroll;
  scrollbar-width: thin;
}

.CategoryBlock>*:first-child {
  margin-left: auto;
}

.CategoryBlock>*:last-child {
  margin-right: auto;
}

.CategoryBlock button {
  font-family: "Roboto", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  font-size: 2rem;
  border: 0;
  color: #2e652d;
  background-color: white;
  position: relative;
}

.CategoryBlock button:hover {
  color: #51b44f
}

.Categories {
  position: absolute;
  display: none;
  border: #2e652d;
  background-color: hsl(0, 0%, 95%);
  border-color: #2e652d;
  border-style: solid;
  border-width: thin;
  max-width: 10rem;
}

.DropDown a {
  display: block;
  text-decoration: none;
  color: #63815f;
  font-family: "Roboto", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
  font-size: 1.125rem;
  margin: 0rem 0.5rem 1rem 0.5rem;
}

.DropDown a:hover {
  text-decoration: underline;
}

.DropDown:hover .Categories {
  display: block;
}
<nav class="CategoryBlock">
  <div class="DropDown">
    <button>
            Articles
        </button>
    <nav class="Categories">
      <a></a>
      <a href="">Feature articles</a>
      <a href="">Opinion articles</a>
      <a href="">Informative</a>
    </nav>
  </div>
  <div class="DropDown">
    <button>
            Short stories
        </button>
    <nav class="Categories">
      <a></a>
      <a href="">Narratives</a>
      <a href="">Retellings</a>
      <a href="">Poetic</a>
    </nav>
  </div>
  <div class="DropDown">
    <button>
            Essays
        </button>
    <nav class="Categories">
      <a></a>
      <a href="">Persuassive</a>
      <a href="">Summaries</a>
      <a href="">Book reviews</a>
    </nav>
  </div>
  <div class="DropDown">
    <button>
            Debates
        </button>
  </div>
  <div class="DropDown">
    <button>
            Scientific reports
        </button>
    <nav class="Categories">
      <a></a>
      <a href="">Physics</a>
      <a href="">Chemistry</a>
    </nav>
  </div>
  <div class="DropDown">
    <button>
            Devlogs
        </button>
    <nav class="Categories">
      <a></a>
      <a href="">Physom</a>
      <a href="">Automatic Page Generator</a>
    </nav>
  </div>
  <div class="DropDown">
    <button>
            Programs
        </button>
    <nav class="Categories">
      <a></a>
      <a href="">Physom</a>
      <a href="">Automatic Page Generator</a>
      <a href="">Raytracer</a>
      <a href="">3D renderer</a>
    </nav>
  </div>
  <div class="DropDown">
    <button>
            Math
        </button>
    <nav class="Categories">
      <a></a>
      <a href="">Discrete</a>
      <a href="">Linear Algebra</a>
      <a href="">PreCalculus</a>
      <a href="">Calculus</a>
    </nav>
  </div>
</nav>

I have a dropdown box that won't move with the buttons as they are scrolled into a different position, this is because the dropdown box is absolute,and I have no idea how to: keep the absolute behaviour, but sort of anchor it to the button itself, rather than its own absolute positioning, basically I want to create a dropdown box that is under the button, but also acts absolute and effects no margins, i tried making the category block relative, but that just added a overflow-y rather than the effect I was looking for.

It would be very helpful if someone pointed me in the right direction!


Solution

  • The problem you are facing is that when you scroll past the right fold on the page horizontally, the browser is calculating the position of the absolute dropdown element prior to the scroll, in relation to the element before it was scrolled. It then places the dropdown elements list in that position relative to the browser window despite whether you have scrolled the button element to the right.

    To fix this you can use some javascript to detect where the button element is on mouseover. Then pass that to your CSS left position for the child dropdown elements with a cssStyleDeclaration method, setProperty() using a CSS variable.

    The snippet below is a rough example of how you can control the dropdown elements position with Javascript using the position of the buttons element taken on mouseover, whether the NAV buttons are scrolled or not. I have included a scrollIntoView() method for button elements that are hovered while overflow is present.

    I have included further explanation within the snippet.

    // get the dropdown nodeLists
    const dropBtns = document.querySelectorAll('.DropDown button');
    // iterate over the nodelist 
    dropBtns.forEach(btn => {
      // add a mouseover eventListener and pass the event
      btn.addEventListener('mouseover', (e) => {
        // query the Categories within the eventListeners parent element
        // using closest()
        const category = btn.closest('.DropDown').querySelector('.Categories');
        // get the height of the NAV so the dropdowns top position 
        // nca be placed just below the scroller for the NAV buttons
        const navHeight = e.target.closest('.CategoryBlock').getBoundingClientRect().height;
        // skip the elements (buttons) without a dropdown
        if (category) {
          // buffer for elements on the edge of the windows page
          const buffer = 8;
          // get the boundingClientRect for the eventTarget
          const categoryRect = e.target.getBoundingClientRect();
          // get the boundingClientRect for the dropdown elements
          const dropdownWidth = category.getBoundingClientRect().width
          // set the initial left position for elements
          let categoryPosition = categoryRect.left;
          // conditional to check for overflowing dropdown elements
          // is the dropdown elements width overflowing past 
          // the left edge of the window?
          if (window.innerWidth < categoryRect.left + dropdownWidth) {
            // if true, set the right edge of the dropdown element to a
            // buffer set to the left of windows innerWidth
            categoryPosition = window.innerWidth - dropdownWidth - buffer;
            // move any nav buttons overflowed element into view
            btn.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" });
            // else if the scrolled button is left of the windows fold
          } else if (categoryRect.left < 0) {
            // set the left of the dropdown to the buffer
            categoryPosition = buffer;
            // move any nav buttons overflowed element into view
            btn.scrollIntoView({ behavior: "smooth", block: "nearest", inline: "nearest" })
          }
          // pass the left position to a CSS variable using the root 
          // this will be used in CSS to set the .Categories left position
          // var(--scrolled-left)
          document.documentElement.style.setProperty('--scrolled-left', `${categoryPosition}px`);
          // set the position of the top of the dropdown
          // to below the scrollbar on the nav section
          document.documentElement.style.setProperty('--top-pos', `${navHeight + buffer}px`);
        }
      })
    })
    .CategoryBlock {
      margin-top: 0.5rem;
      gap: 1rem;
      display: flex;
      overflow-x: scroll;
      scrollbar-width: thin;
    }
    
    .CategoryBlock>*:first-child {
      margin-left: auto;
    }
    
    .CategoryBlock>*:last-child {
      margin-right: auto;
    }
    
    .CategoryBlock button {
      font-family: "Roboto", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
      font-size: 2rem;
      border: 0;
      color: #2e652d;
      background-color: white;
      position: relative;
    }
    
    .CategoryBlock button:hover {
      color: #51b44f
    }
    
    .Categories {
      position: absolute;
      left: var(--scrolled-left);
      top: var(--top-pos);
      display: none;
      border: #2e652d;
      background-color: hsl(0, 0%, 95%);
      border-color: #2e652d;
      border-style: solid;
      border-width: thin;
      max-width: 10rem;
    }
    
    .DropDown a {
      display: block;
      text-decoration: none;
      color: #63815f;
      font-family: "Roboto", "Helvetica Neue", "Helvetica", "Arial", sans-serif;
      font-size: 1.125rem;
      margin: 0rem 0.5rem 1rem 0.5rem;
    }
    
    .DropDown a:hover {
      text-decoration: underline;
    }
    
    .DropDown:hover .Categories {
      display: block;
    }
    <nav class="CategoryBlock">
      <div class="DropDown">
        <button>
          Articles
        </button>
        <nav class="Categories">
          <a></a>
          <a href="">Feature articles</a>
          <a href="">Opinion articles</a>
          <a href="">Informative</a>
        </nav>
      </div>
      <div class="DropDown">
        <button>
          Short stories
        </button>
        <nav class="Categories">
          <a></a>
          <a href="">Narratives</a>
          <a href="">Retellings</a>
          <a href="">Poetic</a>
        </nav>
      </div>
      <div class="DropDown">
        <button>
          Essays
        </button>
        <nav class="Categories">
          <a></a>
          <a href="">Persuassive</a>
          <a href="">Summaries</a>
          <a href="">Book reviews</a>
        </nav>
      </div>
      <div class="DropDown">
        <button>
          Debates
        </button>
      </div>
      <div class="DropDown">
        <button>
          Scientific reports
        </button>
        <nav class="Categories">
          <a></a>
          <a href="">Physics</a>
          <a href="">Chemistry</a>
        </nav>
      </div>
      <div class="DropDown">
        <button>
          Devlogs
        </button>
        <nav class="Categories">
          <a></a>
          <a href="">Physom</a>
          <a href="">Automatic Page Generator</a>
        </nav>
      </div>
      <div class="DropDown">
        <button>
          Programs
        </button>
        <nav class="Categories">
          <a></a>
          <a href="">Physom</a>
          <a href="">Automatic Page Generator</a>
          <a href="">Raytracer</a>
          <a href="">3D renderer</a>
        </nav>
      </div>
      <div class="DropDown">
        <button>
          Math
            </button>
        <nav class="Categories">
          <a></a>
          <a href="">Discrete</a>
          <a href="">Linear Algebra</a>
          <a href="">PreCalculus</a>
          <a href="">Calculus</a>
        </nav>
      </div>
    </nav>