Search code examples
jqueryscrolloffsetscrolltopsticky

Add class when element hits top of page AND remove class when element reaches bottom of parent


What I am trying to achieve:

  • When the top of an image reaches the top of window (by scrolling), a class "sticky" is added to the element
  • When that image that is now sticky reaches the bottom of its container, the class is removed and the element stays at that last bottom position
  • When a user moves back up, the same process happens, only starting from the bottom, going to the top.

Fiddle.

Relevant jQuery:

$(".entry-thumbnail").each(function () {
    var $this = $(this),
        p = $this.parent(),
        scrollT = $this.scrollTop(),
        scrollW = $(window).scrollTop(),
        scrollPT = p.scrollTop() + p.outerHeight();

    if (!$this.hasClass("sticky")) {
        console.log("Tada!");
        $(window).scroll(function () {
            if (scrollT <= scrollW) {
                $this.addClass("sticky");
            }
        });
    }
    else {
       $(window).scroll(function () {
            if (scrollT + $this.outerHeight() >= scrollPT) {
                $this.removeClass("sticky");
            }
        });
    }
});

Problems that I encounter:

  • Class is added to all elements
  • Elements position (left/right) changes when adding the class
  • Class doesn't get removed

Solution

  • I took a different approach to your problem. I calculate the absolute height required inside the window.scroll event handler and then set the height to the appropriate article based on the scroll height.

    Check the demo http://jsfiddle.net/eh3sT/ and let me know if it works for you.

    Edit: You did fix most part of the issue yourself. Thanks :)

    I don't think there is much to optimize, but I just modified a little to improve the readability. Check the full demo at http://jsfiddle.net/eh3sT/3/embedded/result/

    Full Code:

    var etPos = []
    var $articles = $("article");
    $articles.each(function () {
        var tPos = $(this).offset();
        etPos.push([tPos.top, tPos.top + $(this).height(), $(this).find('.entry-thumbnail').height()]);
    });
    
    $(window).scroll(function () {
        var scrollHeight = $(document).scrollTop();
        var cssProp = {};
        for (var i = 0; i < etPos.length; i++) {
            var $atricle = $articles.eq(i);
            if (scrollHeight >= (etPos[i][0]) && scrollHeight <= (etPos[i][1] - etPos[i][2] - 20)) {
                cssProp.top = scrollHeight - etPos[i][0] + 10;
                cssProp.bottome = "auto";
            } else if (scrollHeight > (etPos[i][1] - etPos[i][2] - 20) && $atricle.height() > ($atricle.find('.entry-thumbnail').height() + 10)) {
                /*
                Remove the second condition if the article height is always > respective thumbnail + 10. we don't want set bottom 10, if the thumbnail has no space to scroll.
                */
                 cssProp.top = "auto";
                 cssProp.bottom = 10;
            }else {
                cssProp.top = 10;
                cssProp.bottom = "auto";
            }
            $atricle.find('.entry-thumbnail').css(cssProp);
        }
    });
    

    Notes: Added article class to all article tags.