Getting a sticky header to "push up", like in Instagram's iPhone app using CSS and jQuery

Ryan Rich picture Ryan Rich · Nov 7, 2012 · Viewed 41k times · Source

The Instagram app has a nice sticky header that pushes the current one up in place of the new one. I found a great tutorial on how to do this natively for Android, but I'm looking to do it with JavaScript and CSS.

I was able to get my header to switch out for a new one, but I can't seem to find a way to mimic the way Instagram does it. Any help is greatly appreciated.

*Edit: I was able to get the header to stick to the top of the page when scrolling using waypoints as Cj in the comments pointed out. (link to waypoints). The main issue I'm having is getting the "push up" effect that instagram uses in their mobile app for iPhone. I would link to an example but I've never seen it used before.*

**Edit 2: Using parts of the codepen that @Chris provided I was able to get the headers to stick. I then added a .slideUp effect. My issue now is getting the .slideUp effect to only happen when the next header is reached. Right now the effect activates on scroll.

Here is the code:

(function() {
function stickyTitles(stickies) {
    this.load = function() {
        stickies.each(function(){
            var thisSticky = jQuery(this);
            jQuery.data(thisSticky[0], 'pos', thisSticky.offset().top);
        });
    }
    this.scroll = function() {      
        stickies.each(function(){           
            var thisSticky = jQuery(this),          
                pos = jQuery.data(thisSticky[0], 'pos');
            if (pos <= jQuery(window).scrollTop()) {
                thisSticky.addClass("fixed");
                // added this 
                 $(".followMeBar:parent").slideUp();

            } else {
                thisSticky.removeClass("fixed");
            }
        });         
    }
}
jQuery(document).ready(function(){
    var newStickies = new stickyTitles(jQuery(".followMeBar"));
    newStickies.load();
    jQuery(window).on("scroll", function() {
        newStickies.scroll();

    }); 
});

})();

Answer

Chris Spittles picture Chris Spittles · Nov 8, 2012

There's not a quick or easy answer to this but with a bit of creative cajoling we can emulate the same functionality.

What we need is a series of elements we can identify, loop over and then set up so that when we hit their position on the page the previous item is pushed up and the new item becomes fixed. We will need to retrieve the element's initial position using jQuery's offset().top method and store it in a data tag so we can reference it later. Then the rest will be calculated as we scroll.

This should do the trick:

var stickyHeaders = (function() {

  var $window = $(window),
      $stickies;

  var load = function(stickies) {

    if (typeof stickies === "object" && stickies instanceof jQuery && stickies.length > 0) {

      $stickies = stickies.each(function() {

        var $thisSticky = $(this).wrap('<div class="followWrap" />');
  
        $thisSticky
            .data('originalPosition', $thisSticky.offset().top)
            .data('originalHeight', $thisSticky.outerHeight())
              .parent()
              .height($thisSticky.outerHeight()); 			  
      });

      $window.off("scroll.stickies").on("scroll.stickies", function() {
		  _whenScrolling();		
      });
    }
  };

  var _whenScrolling = function() {

    $stickies.each(function(i) {			

      var $thisSticky = $(this),
          $stickyPosition = $thisSticky.data('originalPosition');

      if ($stickyPosition <= $window.scrollTop()) {        
        
        var $nextSticky = $stickies.eq(i + 1),
            $nextStickyPosition = $nextSticky.data('originalPosition') - $thisSticky.data('originalHeight');

        $thisSticky.addClass("fixed");

        if ($nextSticky.length > 0 && $thisSticky.offset().top >= $nextStickyPosition) {

          $thisSticky.addClass("absolute").css("top", $nextStickyPosition);
        }

      } else {
        
        var $prevSticky = $stickies.eq(i - 1);

        $thisSticky.removeClass("fixed");

        if ($prevSticky.length > 0 && $window.scrollTop() <= $thisSticky.data('originalPosition') - $thisSticky.data('originalHeight')) {

          $prevSticky.removeClass("absolute").removeAttr("style");
        }
      }
    });
  };

  return {
    load: load
  };
})();

$(function() {
  stickyHeaders.load($(".followMeBar"));
});
.followMeBar {
  background: #999;
  padding: 10px 20px;
  position: relative;
  z-index: 1;
  color: #fff;
}
.followMeBar.fixed {
  position: fixed;
  top: 0;
  width: 100%;
  box-sizing: border-box;
  z-index: 0;
}
.followMeBar.fixed.absolute {
  position: absolute;
}
/* For aesthetics only */

body {
  margin: 0;
  font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="followMeBar">A</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">B</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">C</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">D</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">E</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">F</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">G</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">H</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">I</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">J</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">K</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">L</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">M</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">N</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">O</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">P</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">Q</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">R</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">S</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">T</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">U</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">V</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">W</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">X</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">Y</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<div class="followMeBar">Z</div>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>
<br>

Here's the CSS only version:

Before you say "What?! I just went through all of that when there's a CSS only version?!" It only works in a couple of browsers. Try this in firefox for example:

.sticky {
  position: -webkit-sticky;
  position: -moz-sticky;
  position: -o-sticky;
  position: -ms-sticky;
  position: sticky;
  top: 0;
  left: 0;
  right: 0;
  display: block;
  z-index: 1;
  background: #999;
  color: #fff;
  padding: 10px 20px;
}

/* For aesthetics only */
body {
  margin: 0;
  font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif;
}
<div data-lorem="p">
  <span class="sticky">a</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">b</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">c</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">d</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">e</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">f</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">g</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>
<div data-lorem="p">
  <span class="sticky">h</span>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
  <br>
</div>

http://caniuse.com/#feat=css-sticky