skip to Main Content

I’ve got a navigation component that consists of a skinny banner (that should hide on toward scroll) and a main-nav that should then stick to top & and shrink.

I’ve followed the popular answer on using Intersection Observer:
Event to detect when position:sticky is triggered

But the issue with that solution is that my child elements (flex) cause a flicker when going between the hidden and shown sticky banner. I can’t remove those child elements for obvious reasons so instead I’m opting for a position: fixed on the main-nav with a top: 40px. This means the skinny-banner scrolls away as desired, but I need help getting the scrollbar position with Javascript, and then adding a class like (.isSticky when the skinny-banner is no longer there to ensure the main-nav sticks to the top).

.isSticky {
 top: 0;
 height: 66px;
}
body { 
 margin: 0;
 height: 200vh;  
}

.skinny-banner{
  background: lightblue;
  height: 40px;
  display: flex;
}

.nav-menu {
  display: flex;
}

.sticky-nav{
  position: fixed;
  top: 40px;                    


  background: salmon;
  transition: .1s;
}

/* styles for when the header is in sticky mode */
.sticky-nav.isSticky{
  top: 0;
  height: 66px;
}
<header>
   <div class="skinny-banner">Skinny banner that on scroll down disapears.</div>
   <div class="sticky-nav">Sticky Header that on scroll down sticks to top and shrinks in height when stuck</div>
</header>

I’m hoping to use a vanilla JS, HTML & CSS solution & want to maintain the HTML structure with a wrapping container with the skinny-banner and nav-menu as children.

3

Answers


  1. It seems easier to set up a JavaScript listening to the scroll event on the window and updating the isSticky class accordingly based on the scroll position.

    I also removed the transition, in case it participates in the flicker you see.

    body {
      margin: 0;
      height: 200vh;
    }
    
    .skinny-banner {
      background: lightblue;
      height: 40px;
      display: flex;
    }
    
    .nav-menu {
      display: flex;
    }
    
    .sticky-nav {
      background: salmon;
      padding: 0 16px;
    }
    
    
    /* styles for when the header is in sticky mode */
    
    .wrapper.isSticky .sticky-nav {
      position: fixed;
      top: 0;
      height: 66px;
      width: 100%;
    }
    
    .wrapper.isSticky .skinny-banner {
      display: none;
    }
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    
    <body>
      <header>
        <div class="wrapper">
          <div class="skinny-banner">Skinny banner that on scroll down disappears.</div>
          <div class="sticky-nav">Sticky Header that on scroll down sticks to top and shrinks in height when stuck</div>
          <div>Test content 1</div>
          <div>Test content 2</div>
          <div>Test content 3</div>
        </div>
      </header>
      <script>
        document.addEventListener('DOMContentLoaded', function() {
          const wrapper = document.querySelector('.wrapper');
          const skinnyBanner = document.querySelector('.skinny-banner');
          let previousScrollY = window.scrollY;
    
          function updateSticky() {
            const currentScrollY = window.scrollY;
    
            if (currentScrollY > skinnyBanner.offsetHeight) {
              wrapper.classList.add('isSticky');
            } else {
              wrapper.classList.remove('isSticky');
            }
    
            previousScrollY = currentScrollY;
          }
    
          window.addEventListener('scroll', updateSticky);
        });
      </script>
    </body>
    
    </html>

    To make sure the red banner sticky-nav should stick to the top even when scrolling up, the isSticky class is added to the wrapper element as soon as the user scrolls past the height of the skinny-banner, making the sticky-nav stick to the top regardless of the scroll direction.

    Login or Signup to reply.
  2. Thank you for sharing your experience and insights! Please try out the following solution that might be helpful.

      window.onscroll = function() {stickyNav()};
    
      function stickyNav() {
        let navbar = document.querySelector(".sticky-nav");
        let banner = document.querySelector(".skinny-banner");      
        let sticky = navbar.offsetTop;
        if (window.pageYOffset >= sticky) {
          navbar.classList.add("isSticky");
      banner.classList.add("hide-banner"); 
    
        } else {
          navbar.classList.remove("isSticky");
          banner.classList.remove("hide-banner");
        }
      }
        body { 
          margin: 0;
          height: 200vh;  
        }
    
        .skinny-banner {
          background: lightblue;
          height: 40px;
          display: flex;
        }
    
        .nav-menu {
          display: flex;
        }
    
        .sticky-nav {
          position: fixed;
          top: 0px;
          background: salmon;
          transition: .1s;
        }
    
        /* styles for when the header is in sticky mode */
        .sticky-nav.isSticky {
          top: 0;
          height: 77px;
        }
    
        .hide-banner {
        display: none;
         }
    
        div {
          padding: 20px;
          background-color: #f2f2f2;
          border: 1px solid #ddd;
          margin: 10px;
        }
    <div class="sticky-nav">Sticky Header that on scroll down sticks to top and shrinks in height when stuck</div>
    <div class="skinny-banner">Skinny banner that on scroll down disapears.</div>      
    <div>Test Content</div>
    <div>Test Content</div>
    <div>Test Content</div>
    Login or Signup to reply.
  3. The reason for .sticky-nav fails to get sticky is because it is inside of the <header> element. That element has a default display: block according to w3school. So it will block the sticky behavior for its child elements (include position: sticky). To get this done, you should set the display: initial for the <header> element.

    You can also detect when the element gets sticky with the value of .offsetTop. The value will change if the element gets sticky. But, before you should specify position: sticky and top: 0 for .sticky-nav.

    const stickyNav = document.querySelector('.sticky-nav');
    const initalPos = stickyNav.offsetTop;
    
    window.addEventListener("scroll", () => {
      if(stickyNav.offsetTop > initalPos) {
        stickyNav.classList.add('isSticky');
      } else {
        stickyNav.classList.remove('isSticky');
      }
    });
    body { 
     margin: 0;
     height: 200vh;  
    }
    header{
      display: initial;
    }
    .skinny-banner{
      background: lightblue;
      height: 40px;
      display: flex;
    }
    
    .nav-menu {
      display: flex;
    }
    
    .sticky-nav{
      position: sticky;
      top: 0;
      background: salmon;
      min-height: 1px; /* custom this */
      transition: min-height ease 1s; /* custom this */
    }
    
    /* styles for when the header is in sticky mode */
    .sticky-nav.isSticky{
      top: 0;
      min-height: 66px;  /* custom this */
    }
    <header>
       <div class="skinny-banner">Skinny banner that, on scroll down, disappears.</div>
       <div class="sticky-nav">Sticky Header that, on scroll down, sticks to top and shrinks in height when stuck</div>
       <div>Test content 1</div>
       <div>Test content 2</div>
       <div>Test content 3</div>
       <div>Test content 4</div>
       <div>Test content 5</div>
       <div>Test content 6</div>
       <div>Test content 7</div>
       <div>Test content 8</div>
       <div>Test content 9</div>
       <div>Test content 10</div>
       <div>Test content 11</div>
       <div>Test content 12</div>
       <div>Test content 13</div>
       <div>Test content 14</div>
       <div>Test content 15</div>
       <div>Test content 16</div>
    </header>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search