72

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

    }); 
});

})();

Ryan Rich
  • 11,637
  • 8
  • 22
  • 31
  • but if you want something fixed you can have it by position:fixed in css – Dhruvenkumar Shah Nov 07 '12 at 23:00
  • 2
    try this: http://imakewebthings.com/jquery-waypoints/sticky-elements/ – Amir Nov 07 '12 at 23:03
  • Consider to use Bootstrap, it is heavy, but have many cool stuff as well as sticky header. – Dan K.K. Nov 07 '12 at 23:44
  • @Danijar Does Bootstrap actually have sticky section headers? I only recall a sticky page header there. (And affix, but that's not really what the OP wants I think.) – millimoose Nov 08 '12 at 00:06
  • @Ryan - Is this for a mobile site? – gilly3 Nov 08 '12 at 00:09
  • @CjCoax , I was able to get the header to stick to the top. However if you look at the instagram app for iPhone, you'll see how they have the header stuck to the top of the page and as you scroll to a new header the old one gets "pushed" up out of the way. That pushing up part is what I'm having trouble with. – Ryan Rich Nov 08 '12 at 00:42
  • @gilly3 , No this is for a desktop website. – Ryan Rich Nov 08 '12 at 00:42
  • Yup! Take a look at [examples](http://twitter.github.com/bootstrap/getting-started.html#examples) – Dan K.K. Nov 08 '12 at 11:06
  • I was looking to do something similar to this. Using the ideas above, I created a jQuery plugin to do the hefty lifting. It also works inside an overflow div. https://github.com/Hypnopompia/jquery-stickyheaders – Hypnopompia Mar 03 '14 at 15:31

8 Answers8

134

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

Chris Spittles
  • 15,023
  • 10
  • 61
  • 85
  • Chris, I was able to get that to work before. However when I scroll back up it skips the other headers and jumps to the top, instead of replacing it with the previous one. – Ryan Rich Nov 08 '12 at 19:42
  • Sorry I was pressed for time and didn't notice it not "docking". I've modified the script. The problem was I needed to store the original position to evaluate against. So I've used jQuery.data to store the element's original top position in the ready event. then when the page is scrolled it evaluates the windows offset against that of the pos we stored on the element. if its less than or equal we add a class of fixed otherwise we remove it. – Chris Spittles Nov 09 '12 at 08:46
  • Thanks for staying up with this. While your headers do switch off, they do not "push up" like in the Instagram iPhone app which is what I'm really looking for. – Ryan Rich Nov 11 '12 at 03:20
  • I've added some modifications. How about now? – Chris Spittles Nov 12 '12 at 11:17
  • @ChrisAnyway you could see why this won't work? I was playing around with the solution you provided while trying to get a div about the headers to always be fixed so that the headers push up like you showed but they go under the fixed div. Here's the code pen I was playing with http://codepen.io/anon/full/sAHuy – Ryan Rich Nov 20 '12 at 23:55
  • You need to use the followMeBar class on any elements you want to move down the page. Also you've changed the CSS position attributes which will break it. – Chris Spittles Feb 24 '13 at 08:07
  • @ryanSrich were you ever able to figure out how to do what you were trying to do in this codepen? codepen.io/anon/full/sAHuy It would be super helpful if you could post your solution. thank you. – Hung Luu May 29 '13 at 23:40
  • 1
    this is not a very elegant solution but i hardcoded 50px to the fixed top positioning and then adjusted the checks for when the element should become a fixed element (line 24) by 50px, the check for the absolute positioned wrap by 50px (line 28) and where the absolute positioned wrap should lose its absolute positioning by 50px (line 38). saved here: http://codepen.io/seraphzz/pen/GpeCi – Hung Luu May 29 '13 at 23:57
  • 3
    Chris, love this code! For anyone who is wondering I was able to get this code to work within a scrolling `
    ` by changing the `offset().top` references to `position().top` and attaching the scroll events to the `
    ` instead of the `window`. You may have to tweak the CSS a bit, but it works great!
    – Heath Jul 17 '13 at 17:22
  • 2
    I've adapted Chris's codepen to work inside a scrollable div http://codepen.io/jakeyadda/pen/kaFhI – Mr_Chimp Sep 05 '13 at 14:27
  • Has anyone figured out how to do this with elements that are adding dynamically, a la infinite scroll? – jetlej Aug 24 '14 at 02:23
  • 1
    @jetlej You would have to restructure the example to allow the addition of elements like this: http://codepen.io/chrissp26/pen/vfgwb – Chris Spittles Aug 26 '14 at 09:29
  • 2
    @ChrisSpittles - Thanks so much! I just had to add some logic to check if the position of a sticky had already been determined, otherwise it would re-position it incorrectly if you were scrolled down the page at the time. Full code for other's needing it: http://codepen.io/anon/pen/blJrC – jetlej Aug 28 '14 at 18:59
  • 1
    I needed a version that could tolerate adding, removing, expanding, and collapsing sections. See http://codepen.io/RickMohr/pen/yNexrX – Rick Mohr May 11 '15 at 16:23
  • @Cris .. the code is working excellent.I have page with fixed header, in that case is there any way to fix it below the header.now the half part of the sticky div is inside the header. – Warrior Jul 21 '15 at 10:09
  • @Warrior I'm not sure what you mean, do you have an example? – Chris Spittles Jul 21 '15 at 22:24
  • @cris. you can see static header in this page.While scrolling the half part of the sticky div is inside the header.plz look this link https://c5dc91228970ddecd8db9647d55f07fa80373e1c.googledrive.com/host/0B4hL8nvw_9Thfkk0eDQ5UzBaRDRMZ2hPS2FlMU5UVUxyUUxQQXpCT3J3S3kxYkU0SVFUbzA/#s – Warrior Jul 22 '15 at 12:31
  • @Warrior I see what you mean. Its because your static header at the top is `position: fixed`. You will need to account for the height of this header when setting the offset value. It might be worth altering the script to accept an additional parameter called offset which you would set as the header height. Then you could add that value on to the offset.top value and it "should" work. – Chris Spittles Jul 23 '15 at 06:33
  • @cris I tried but the push up effect is missing. If you dont mind can u change the script. – Warrior Jul 23 '15 at 07:31
  • The original and all variants on that script are great in their own right. One note however is that the data attributes won't work since they are camelCased and must be lowercase. (and also should be set using attr();) Nice work tho. – Rick Davies Dec 06 '15 at 02:09
  • What if it's inside a div with scroll instead of a window? I tried to work around it but wasn't working for me... – SearchForKnowledge Sep 14 '16 at 18:32
7

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

james
  • 4,150
  • 2
  • 30
  • 36
  • 1
    Great! Much simpler, and should be faster too. One problem though: How do we prevent the bouncy scroll problem we see on mobile devices? Just load the sample page http://embed.plnkr.co/wk3h40LfBdN1UFtDLZgY/preview on a phone and see what I mean – philk Oct 18 '15 at 22:54
  • This has a problem when using with bootstrap, see https://jsfiddle.net/pzquzLxh/2/ – Akshay Aug 28 '17 at 10:39
  • @akshay the OP didn't ask for a solution with bootstrap – james Sep 11 '17 at 19:22
3

I tried solutions provided above but none of them working perfectly without bug (mostly bug on scrolling and browser compatibility). Just found this one https://github.com/sarahdayan/feedify and it's working good for me.

webchun
  • 1,204
  • 2
  • 13
  • 30
2

Make sure to wrap it in div

Notice the 6th heading as it is not wrapped in div

h1, h2{
position: sticky;
top: 0;
}

div{
height: 500px;
}
<div>
<h1>lorem ipsum doloro sit <br> amet 1</h1>
</div>

<div>
<h1>lorem ipsum doloro sit 2</h1>
</div>

<div>
<h2>lorem ipsum doloro sit 3</h2>
</div>

<div>
<h1>lorem ipsum doloro sit <br> amet 4</h1>
</div>

<div>
<h2>lorem ipsum doloro sit <br> amet 5</h2>
</div>

<h2>lorem ipsum doloro sit <br> amet 6</h2>

<div></div>

<div>
<h1>lorem ipsum doloro sit  7</h1>
</div>

<div>
<h1>lorem ipsum doloro sit <br> amet 8</h1>
</div>

<div>
<h1>lorem ipsum doloro sit <br> amet 9</h1>
</div>
mmmcho
  • 209
  • 2
  • 7
1

I was having trouble getting Chris' answer to work accurately for me as all the stickies were inside a relatively positioned div (with a header above it all, outside of the relative div) - the answer to this is just storing the .offset().top of the relative container div in a var and subtracting it from the .css('top', value) in the script. As in chris' version, the top value is pushing off the top of the document which works fine. When you add in the relative div, top is now pushing from the top of that instead, so any space above the relative div is included in the value which is something you wouldn't want. Hope this helps someone else. James

  • Also, I am having a problem when refreshing the page when scrolled halfway down the page. This seems to interfere with jquery getting the proper offset.top value so that when its reloaded, the stickies are offset in weird ways. (If I figure out a solution I'll post here) – user2668414 Mar 13 '14 at 15:46
  • Okay, so apparently, the problem was just in chrome (webkit browser) - I handled it by wrapping the whole thing in $(window).load() as the answer is in this post: http://stackoverflow.com/questions/2173040/jquery-position-problem-with-chrome – user2668414 Mar 13 '14 at 19:33
0

A basic version of the solution:

CSS

.sectionWrapper{
    background-color: #1E1E1E;
    padding: 10px 20px;
    color: #FF7B05;
    width: 100%
    height: 45px;
    min-height: 45px;

    margin-top: 30px;   
    position: relative;
    z-index: 10;
}
.sectionHeader{
}
.sectionEmbed {
}
.sectionFixed {
    background-color: #1E1E1E;
    padding: 10px 20px;
    color: #FF7B05;
    width: 100%;
    height: 45px;

    position: fixed;
    top: 0;
    left: 0;
    margin: 0;
}

HTML

<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 sectionWrapper sectionEmbed">
    <div class="sectionHeader">Headers here</div>
</div>

JQuery

//check doc is ready
$(function() {
    $(window).on("scroll.sectionWrapper", function() {
        stickSections();
    });
});

//function stickSection
function stickSections()
{
    //detach the listener
    $(window).off("scroll.sectionWrapper");

    $scrollPos = $(window).scrollTop();
    $currentSticky = null;

    $('.sectionWrapper').each(function(){

        $(this).children().removeClass('sectionFixed');     

        if($scrollPos >= ($(this).offset().top - ($(this).height() - $(this).children().height() - 6)) ){
            $currentSticky = $(this);
        }
        else $(this).children().addClass('sectionEmbed');
    });

    //apply the fixed class to our scrollPos match
    if($currentSticky) $currentSticky.children().addClass('sectionFixed');

    //reattach the listener
    $(window).on("scroll.sectionWrapper", function() {
        stickSections();        
    });
}

EXAMPLE

https://codepen.io/ZombiesByte/pen/EyYwVO

Have fun :)

Dal
  • 73
  • 2
  • 6
0

This doesn't always work well if there's content above the div with the sticky headers: $thisSticky.css('transform', 'translateY(' + $newPosition+ 'px)');

But this line does the trick //could just as easily use top instead of transform $thisSticky.css('top', $newPosition+ 'px');

spnkr
  • 952
  • 9
  • 18
0

The solution @chris-spittles provided helped me out a lot but I want to also provide a vanilla Javascript solution. Additionally, I've added functionality that handles the last sticky for situations where you don't want it to continue to be fixed past it's content area.

https://codepen.io/jamigibbs/pen/QZWjBy

const StickySubsections = (function(){
  let el

  return {
    elements: function () {
      return {
        stickies: [...document.querySelectorAll('.followMeBar')]
      }
    },

    init: function () {
      el = this.elements()
      this.load()
    },

    load: function () {
      this.setupStickyWrap()
      window.addEventListener('scroll', () => this.whenScrolling())
    },

    setupStickyWrap: function(){
      el.stickies.forEach((sticky, i) => {
        const stickyWrap = this.addWrap(sticky, 'sticky-wrap')
        const heightToTop = sticky.getBoundingClientRect().top + window.scrollY
        const outerHeight = sticky.offsetHeight

        stickyWrap.parentElement.id = `sticky-content-${i}`
        sticky.setAttribute('data-originalPosition', heightToTop)
        sticky.setAttribute('data-originalHeight', outerHeight)

        stickyWrap.style.height = outerHeight + 'px'
      })
    },

    addWrap: function(el, className, wrap = 'div') {
      const wrapper = document.createElement(wrap)
      wrapper.classList.add(className)
      el.parentNode.insertBefore(wrapper, el)
      wrapper.appendChild(el)
      return wrapper
    },

    elementExists: function(el){
      return typeof(el) != 'undefined' && el != null
    },

    stickyPosition: function(el){
      return el.getAttribute('data-originalPosition')
    },

    nextStickyPosition: function(current, next){
      return next.getAttribute('data-originalPosition') - current.getAttribute('data-originalHeight')
    },

    scrollingPositionTop: function(el){
      return el.getBoundingClientRect().top + window.scrollY
    },

    offsetTop: function(el){
      return el.getBoundingClientRect().top
    },

    scrollingPositionBottom: function(el){
      return el.getBoundingClientRect().bottom + window.scrollY
    },

    headerPosition: function(){
      return window.scrollY
    },

    bottomSectionHit: function(contentElement, sticky){
      const contentSection = document.getElementById(contentElement)
      const sectionBottom = contentSection.getBoundingClientRect().bottom + window.scrollY
      const stickyPositionScrolling = sticky.getBoundingClientRect().bottom + window.scrollY

      return stickyPositionScrolling >= sectionBottom
    },

    whenScrolling: function() {
      el.stickies.forEach((sticky, i) => {
        const nextSticky = el.stickies[i + 1]
        const prevSticky = el.stickies[i - 1]

        if (this.stickyPosition(sticky) <= this.headerPosition()) {
          sticky.classList.add('fixed')

          if (this.elementExists(nextSticky)) {

            while (this.scrollingPositionBottom(sticky) >= this.nextStickyPosition(sticky, nextSticky) + 50) {
              sticky.classList.add('absolute')
              sticky.style.top = this.nextStickyPosition(sticky, nextSticky)
            }

          // Handle last sticky element
          } else {
            if (this.bottomSectionHit(`sticky-content-${i}`, sticky)) {
              sticky.classList.remove('fixed')
            }
          }

        } else {

          sticky.classList.remove('fixed')

          if (this.elementExists(prevSticky) && window.scrollY <= this.stickyPosition(sticky)){
            prevSticky.classList.remove('absolute')
            prevSticky.removeAttribute('style')
          }
        }
      })
    }

  }
}())

StickySubsections.init()
jami0821
  • 301
  • 2
  • 16