10

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?

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
Timo Mämecke
  • 287
  • 2
  • 14

1 Answers1

13

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.

Roko C. Buljan
  • 196,159
  • 39
  • 305
  • 313
  • Thanks man, works great! I hoped it would be possible without a wrapping div to keep a clean markup, but I'll incur that. – Timo Mämecke May 13 '13 at 02:11
  • @TimoM you're welcome! I created a reusable function so you can use it also on `DOM ready`, `window load`, wherever you need. You know, images load slowly and might be useful to re-check the scroll value, cause the page could sometimes be already scrolled while visited. – Roko C. Buljan May 13 '13 at 02:13
  • Yeah, thanks for that! I already wrapped the jQuery into the `DOM ready`, but this `sticky()`-function is really neat! – Timo Mämecke May 13 '13 at 02:21
  • @Roko C. Buljan How would I set the sticky element to stick x-number of pixels below the top? Thnx! –  Dec 03 '13 at 21:59