Search code examples
javascriptjquerycsssticky

Sticky Header - buggy jumping on scroll


I have a specific problem on making a sticky header with jQuery. I tried the commonly used snippets around the web, but I perceived the same buggy thing everywhere.

At a specific document height (scrollable until a little more than calling of sticky-effect) the sticky header jumps between position: fixed and position: static.

HTML:

<header>
  <div id="not-sticky"></div>
  <div id="sticky"></div>
</header>
<div id="content"> ...


jQuery:

var $sticky = $("#sticky");
var offset = $sticky.offset();
var stickyTop = offset.top;
var windowTop = $(window).scrollTop();
$(window).scroll(function() {
  windowTop = $(window).scrollTop();
  if (windowTop > stickyTop) {
    $sticky.css({
      position: 'fixed',
      top: 0
    });
  }
  else {
    $sticky.css({
      position: '',
      top: ''
    });
  }
});


CSS:

header {
  width: 100%;
}

#not-sticky {
  padding: 50px 0;
  width: 100%;
}

#sticky {
  padding: 24px 0;
  position: relative;
  width: 100%;
  z-index: 25;
}


I also tried a margin-bottom on #not-sticky with the same height as the #sticky to keep a constant document-height, but the same jumpy-sticky-effect occurred.

Any idea to fix that thing?


Solution

  • Scroll fires too many times and trying to set an element style will always & inevitably create jumps (even barely noticeable but still jaggy).

    The best way I've found is to

    1. clone our element,
    2. make that clone fixed
    3. play with clone's visibility style.

    Pure JS:

    ;(function(){ /* STICKY */
    
      var sticky  = document.getElementById("sticky"),
          sticky2 = sticky.cloneNode(true);
    
      sticky2.style.position = "fixed";
      document.body.appendChild(sticky2);
    
      function stickIt(){
        sticky2.style.visibility = sticky.getBoundingClientRect().top<0 ? "visible" : "hidden";
      }
    
      stickIt();
      window.addEventListener("scroll", stickIt, false );
    }());
    #sticky{
      height:100px;
      background:#ada;
      height:50px;
      position:relative;
      /* needed for clone: */
      top:0; 
      width:100%;
    }
    
    
    /* Just for this demo: */
    *{margin:0;padding:0;}
    #content{height:2000px; border:3px dashed #444;}
    h1{padding:40px; background:#888;}
    <h1>Logo</h1>
    <div id="sticky">Sticky header</div>
    <div id="content">Lorem ipsum...<br>bla bla</div>

    So when you see the "header" fix, that's actually our fixed clone getting visible on-top.