Sticky sidebar: stick to bottom when scrolling down, top when scrolling up

Here's an example of how to implement this:

JavaScript:

$(function() {

var $window = $(window);
var lastScrollTop = $window.scrollTop();
var wasScrollingDown = true;

var $sidebar = $("#sidebar");
if ($sidebar.length > 0) {

    var initialSidebarTop = $sidebar.position().top;

    $window.scroll(function(event) {

        var windowHeight = $window.height();
        var sidebarHeight = $sidebar.outerHeight();

        var scrollTop = $window.scrollTop();
        var scrollBottom = scrollTop + windowHeight;

        var sidebarTop = $sidebar.position().top;
        var sidebarBottom = sidebarTop + sidebarHeight;

        var heightDelta = Math.abs(windowHeight - sidebarHeight);
        var scrollDelta = lastScrollTop - scrollTop;

        var isScrollingDown = (scrollTop > lastScrollTop);
        var isWindowLarger = (windowHeight > sidebarHeight);

        if ((isWindowLarger && scrollTop > initialSidebarTop) || (!isWindowLarger && scrollTop > initialSidebarTop + heightDelta)) {
            $sidebar.addClass('fixed');
        } else if (!isScrollingDown && scrollTop <= initialSidebarTop) {
            $sidebar.removeClass('fixed');
        }

        var dragBottomDown = (sidebarBottom <= scrollBottom && isScrollingDown);
        var dragTopUp = (sidebarTop >= scrollTop && !isScrollingDown);

        if (dragBottomDown) {
            if (isWindowLarger) {
                $sidebar.css('top', 0);
            } else {
                $sidebar.css('top', -heightDelta);
            }
        } else if (dragTopUp) {
            $sidebar.css('top', 0);
        } else if ($sidebar.hasClass('fixed')) {
            var currentTop = parseInt($sidebar.css('top'), 10);

            var minTop = -heightDelta;
            var scrolledTop = currentTop + scrollDelta;

            var isPageAtBottom = (scrollTop + windowHeight >= $(document).height());
            var newTop = (isPageAtBottom) ? minTop : scrolledTop;

            $sidebar.css('top', newTop);
        }

        lastScrollTop = scrollTop;
        wasScrollingDown = isScrollingDown;
    });
}
});

CSS:

#sidebar {
  width: 180px;
  padding: 10px;
  background: red;
  float: right;
}

.fixed {
  position: fixed;
  right: 50%;
  margin-right: -50%;
}

Demo: http://jsfiddle.net/ryanmaxwell/25QaE/

This works as expected in all scenarios and is well-supported in IE too.


+1 to the very nice and ilustrative image.

I know it's an old question, but I casually found the same question posted by you in forum.jquery.com and one answer there (by@tucker973), suggested one nice library to make this and wanted to share it here.

It's called sticky-kit by @leafo

  • github project
  • webpage
  • simple example in jsFiddle (same code as the snippet attached here)

Here you have the code of a very basic example that I prepared and a working demo to see the result.

/*!
 * Sticky-kit
 * A jQuery plugin for making smart sticky elements
 *
 * Source: http://leafo.net/sticky-kit/
 */

$(function() {
  $(".sidebar").stick_in_parent({
    offset_top: 10
  });
});
* {
  font-size: 10px;
  color: #333;
  box-sizing: border-box;
}
.wrapper,
.header,
.main,
.footer {
  padding: 10px;
  position: relative;
}
.wrapper {
  border: 1px solid #333;
  background-color: #f5f5f5;
  padding: 10px;
}
.header {
  background-color: #6289AE;
  margin-bottom: 10px;
  height: 100px;
}
.sidebar {
  position: absolute;
  padding: 10px;
  background-color: #ccc;
  height: 300px;
  width: 100px;
  float: left;
}
.main {
  background-color: #ccc;
  height: 600px;
  margin-left: 110px;
}
.footer {
  background-color: #6289AE;
  margin-top: 10px;
  height: 250px;
}
.top {
  position: absolute;
  top: 10px;
}
.bottom {
  position: absolute;
  bottom: 10px;
}
.clear {
  clear: both;
  float: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="http://leafo.net/sticky-kit/src/jquery.sticky-kit.js"></script>
<div class="wrapper">
  <div class="header"> <a class="top">header top</a>
    <a class="bottom">header bottom</a>

  </div>
  <div class="content">
    <div class="sidebar"> <a class="top">sidebar top</a>
      <a class="bottom">sidebar bottom</a>

    </div>
    <div class="main"> <a class="top">main top</a>
      <a class="bottom">main bottom</a>

    </div>
    <div class="clear"></div>
  </div>
  <div class="footer"> <a class="top">footer top</a>
    <a class="bottom">footer bottom</a>

  </div>
</div>

Of course, all credits go to the creator of the plugin, I only made this example to show it here. I need to accomplish the same result that you was after and found this plugin very useful.


Thanks for the great graphic. I was also looking for a solution to this challenge!

Unfortunately, the other answer posted here doesn't address requirement #5 that stipulates the ability to scroll back through the sidebar smoothly.

I created a fiddle that implements all requirements: http://jsfiddle.net/bN4qu/5/

The core logic that needs to be implemented is:

If scrolling up OR the element is shorter than viewport Then
  Set top of element to top of viewport If scrolled above top of element
If scrolling down then
  Set bottom of element at bottom of viewport If scrolled past bottom of element

In the fiddle I use CSS3 transform for moving the target element around, so it won't work in e.g. IE<9. The logic is sound though for using a different approach.

Also, I modified your fiddle so that the sticky sidebar has a gradient background. This helps to show that the proper behavior is being exhibited.

I hope this is useful to someone!