Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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();      });  }); 

})();

like image 791
Ryan Rich Avatar asked Nov 07 '12 22:11

Ryan Rich


People also ask

How do I make headers sticky in CSS?

You can easily create sticky or fixed header and footer using the CSS fixed positioning. Simply apply the CSS position property with the value fixed in combination with the top and bottom property to place the element on the top or bottom of the viewport accordingly.

How do I make my top header sticky?

Hover over the header. Click “Header” Select ”Set As Sticky Header”

How to create a sticky menu/header?

After installing and activating the plugin, go to the plugin settings to start creating your first sticky menu/header. It can seem complex at first sight, but it is very simple. The only “code” you’ll need is the names of the elements you want to make sticky. The plugin settings are divided into basic settings and advanced settings.

How to create a sticky widget in WordPress?

Create a sticky header, sticky menu, sticky widget, maybe even a sticky logo, whatever you want. You can download WP Sticky (free version) by going to Plugins, Add New, simply typing in WP Sticky, and then clicking the Install button. You can also buy it (Pro version) via the official website.

What are sticky elements in WordPress?

Along with all of this, WordPress also allows you to create so-called sticky elements. These elements come in all shapes and sizes. But the most popular ones are a sticky header, a sticky menu, and an Elementor sticky menu.

What's the missing feature of CSS position sticky?

An event is the the missing feature of CSS position:sticky. One of the practical limitations of using CSS sticky position is that it doesn't provide a platform signal to know when the property is active.


2 Answers

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

like image 91
Chris Spittles Avatar answered Oct 14 '22 10:10

Chris Spittles


First off, thanks to @Chris Spittles for his excellent answer.

I've created a modified version that removes the need to wrap each sticky element, as it simply changes their relative position instead of using fixed positioning.

var stickyHeaders = (function() {      var $stickies;      var load = function(stickies, target) {          if (typeof stickies === "object" && stickies instanceof jQuery && stickies.length > 0) {              $stickies = stickies.each(function() {                  var $thisSticky = $(this);                  $thisSticky                     .data('originalPosition', $thisSticky.offset().top)                     .data('originalHeight', $thisSticky.outerHeight());                            });              target.off("scroll.stickies").on("scroll.stickies", function(event) {                  _whenScrolling(event);                  });         }     };      var _whenScrolling = function(event) {          var $scrollTop = $(event.currentTarget).scrollTop();          $stickies.each(function(i) {                          var $thisSticky = $(this),                 $stickyPosition = $thisSticky.data('originalPosition'),                 $newPosition,                 $nextSticky;              if ($stickyPosition <= $scrollTop) {                  $newPosition = Math.max(0, $scrollTop - $stickyPosition);                 $nextSticky = $stickies.eq(i + 1);                  if($nextSticky.length > 0) {                      $newPosition = Math.min($newPosition, ($nextSticky.data('originalPosition') -  $stickyPosition) - $thisSticky.data('originalHeight'));                 }              } else {                  $newPosition = 0;             }              $thisSticky.css('transform', 'translateY(' + $newPosition + 'px)');              //could just as easily use top instead of transform             //$thisSticky.css('top', $newPosition + 'px');         });     };      return {         load: load     }; })();  $(function() {     stickyHeaders.load($(".followMeBar"), $(window)); }); 

The css is simplified to:

.followMeBar {     background: #999;     padding: 10px 20px;     position: relative;     z-index: 1;     color: #fff; }  /* For aesthetics only */  body {     margin: 0;     font-family: Segoe, "Segoe UI", "DejaVu Sans", "Trebuchet MS", Verdana, sans-serif; } 

http://plnkr.co/edit/wk3h40LfBdN1UFtDLZgY?p=preview

And here's another example showing how you can have an offset when using a fixed header:

http://plnkr.co/edit/8YBqdCIKruVKYRXbZnCp?p=preview

like image 21
james Avatar answered Oct 14 '22 09:10

james