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

JqueryPositionFixedSidebarSticky

Jquery Problem Overview


I have been looking for some time now for a solution to my sticky sidebar problem. I have a specific idea of how I would like it to act; effectively, I would like it to stick to the bottom as you scroll down, and then as soon as you scroll back up I would like it to stick to the top, in a fluid motion (no jumping). I am unable to find an example of what I am trying to achieve, so I have created an image that I hope will illustrate the point clearer:

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

  1. Sidebar sits under the header.
  2. As you scroll down the sidebar remains level with the content of the page so that you can scroll through both sidebar and content.
  3. Reach the bottom of the sidebar, the sidebar sticks to the bottom of the viewport (most plugins only allow for sticking to top, some that allow for sticking to bottom don't allow for both).
  4. Reach the bottom, sidebar sits above the footer.
  5. As you scroll back up, the sidebar stays level with the content so you can scroll through the content and sidebar again.
  6. Reach the top of the sidebar, the sidebar sticks to the top of the viewport.
  7. Reach the top and the sidebar sits back below the header.

I hope this is enough information. I have created a jsfiddle to test any plugins/scripts, which I have reset for this question: http://jsfiddle.net/jslucas/yr9gV/2/ .

Jquery Solutions


Solution 1 - Jquery

+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

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.

Solution 2 - Jquery

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!

Solution 3 - Jquery

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.

Solution 4 - Jquery

Sample two direction Sticky Sidebar.

If someone needs a lightweight solution not based on jQuery, I invite you to familiarize yourself with this code: Two-direction-Sticky-Sidebar on GitHub.

//aside selector
const aside = document.querySelector('[data-sticky="true"]'), 
//varibles
startScroll = 0;
var endScroll = window.innerHeight - aside.offsetHeight -500,
currPos = window.scrollY;
screenHeight = window.innerHeight,
asideHeight = aside.offsetHeight;
aside.style.top = startScroll + 'px';
//check height screen and aside on resize
window.addEventListener('resize', ()=>{
    screenHeight = window.innerHeight;
    asideHeight = aside.offsetHeight;
});
//main function
document.addEventListener('scroll', () => {
    endScroll = window.innerHeight - aside.offsetHeight;
    let asideTop = parseInt(aside.style.top.replace('px;', ''));
    if(asideHeight>screenHeight){
        if (window.scrollY < currPos) {
            //scroll up
            if (asideTop < startScroll) {
                aside.style.top = (asideTop + currPos - window.scrollY) + 'px';
            } else if (asideTop >= startScroll && asideTop != startScroll) {
                aside.style.top = startScroll + 'px';
            }
        } else {
            //scroll down
            if (asideTop > endScroll) {
                aside.style.top = (asideTop + currPos - window.scrollY) + 'px';
            } else if (asideTop < (endScroll) && asideTop != endScroll) {
                aside.style.top = endScroll + 'px';
            }
        }
    }
    currPos = window.scrollY;
}, {
    capture: true,
    passive: true
});

body{
  padding: 0 20px;
}
#content {
  height: 2000px;
}
header {
  width: 100%;
  height: 150px;
  background: #aaa;
}
main {
  float: left;
  width: 65%;
  height: 100%;
  background: #444;
}
aside {
  float: right;
  width: 30%;
  position: sticky;
  top: 0px;
  background: #777;
}
li {
  height: 50px;
}
footer {
  width: 100%;
  height: 300px;
  background: #555;
  position: relative;
  bottom: 0;
}

<!DOCTYPE html>
    <head>
        <link href="/src/style.css" rel="preload" as="style"/>
    </head>
    <body>
        <header>Header</header>
            <div id="content">
            <main>Content</main>
            <aside data-sticky="true">
              <lu>
                <li>Top</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>sidebar</li>
                <li>Bottom</li>
              </lu>
            </aside>
            </div>
        <footer>Footer</footer>
        <script src='/src/script.js' async></script>
    </body>
</html>

Solution 5 - Jquery

You can also use Sticky Sidebar JS plugin for the same effect you are wanting . It has a small and simple documentation on "How to use". I also wanted the similar scrolling effect and it did work pretty nicely.

https://abouolia.github.io/sticky-sidebar/

Solution 6 - Jquery

Vanilla JS option!

After a while of wanting to do this with Vanilla JS, I've finally cracked it. It definitely could do with some tidying up, but it works!

  const sidebar = document.querySelector('#sidebar');

  let lastScrollTop = 0;
  let scrollingDown;
  let isAbsolute = false;

  let absolutePosition = 0;
  let windowTop;
  let sidebarTop;
  let windowBottom;
  let sidebarBottom;

  function checkScrollDirection() {
    if (lastScrollTop <= window.scrollY) {
      scrollingDown = true
    } else {
      scrollingDown = false
    }
    lastScrollTop = window.scrollY;
  }      

  function fixit(pos,top,bottom,isAb) {

    sidebar.style.position = pos;
    sidebar.style.top = top;
    sidebar.style.bottom = bottom;
    isAbsolute = isAb;
  }

  function scrolling() {
    //optional width check
    if (window.innerHeight <= sidebar.offsetHeight && window.innerWidth > 996) {
      checkScrollDirection();
      windowTop = window.scrollY;
      sidebarTop = sidebar.offsetTop;
      windowBottom = window.scrollY + window.innerHeight;
      sidebarBottom = sidebar.offsetHeight + sidebar.offsetTop;

      if(!scrollingDown && windowTop <= sidebarTop) {
        //fixToTop
        fixit("fixed",0,"unset",false)
      }

      if(scrollingDown && windowBottom >= sidebarBottom) {
        //fixToBottom
        fixit("fixed","unset",0,false)
      }

      if((!isAbsolute && windowTop > sidebarTop) || !isAbsolute && windowBottom < sidebarBottom) {
        //fixInPlace
        let absolutePosition = (windowTop + sidebar.offsetTop) + "px";
        fixit("absolute",absolutePosition,"unset",true)
      }
    }
  }

  window.addEventListener('scroll', scrolling);

Solution 7 - Jquery

I was looking for the exact same thing. Apparently I needed to search for some obscure terms just to find a similar question with the graphic. Turns out it's exactly what I was looking for. I couldn't find any plugins so I decided to make it myself. Hopefully someone will see this and refine it.

Here's a quick and dirty sample html I'm using.

<div id="main">
    <div class="col-1">
    </div>
    <div class="col-2">
        <div class="side-wrapper">
            sidebar content
        </div>
    </div>
</div>

Here's the jQuery I made:

var lastScrollPos = $(window).scrollTop();
var originalPos = $('.side-wrapper').offset().top;
if ($('.col-2').css('float') != 'none') {
	$(window).scroll(function(){
		var rectbtfadPos = $('.rectbtfad').offset().top + $('.rectbtfad').height();
		// scroll up direction
		if ( lastScrollPos > $(window).scrollTop() ) {
			// unstick if scrolling the opposite direction so content will scroll with user
			if ($('.side-wrapper').css('position') == 'fixed') {
				$('.side-wrapper').css({
					'position': 'absolute',
					'top': $('.side-wrapper').offset().top + 'px',
					'bottom': 'auto'
				});
			} 
			// if has reached the original position, return to relative positioning
			if ( ($(window).scrollTop() + $('#masthead').height()) < originalPos ) {
				$('.side-wrapper').css({
					'position': 'relative',
					'top': 'auto',
					'bottom': 'auto'
				});
			} 
			// sticky to top if scroll past top of sidebar
			else if ( ($(window).scrollTop() + $('#masthead').height()) < $('.side-wrapper').offset().top && $('.side-wrapper').css('position') == 'absolute' ) {
				$('.side-wrapper').css({
					'position': 'fixed',
					'top': 15 + $('#masthead').height() + 'px',	// padding to compensate for sticky header
					'bottom': 'auto'
				});
			}
		} 
		// scroll down
		else {
			// unstick if scrolling the opposite direction so content will scroll with user
			if ($('.side-wrapper').css('position') == 'fixed') {
				$('.side-wrapper').css({
					'position': 'absolute',
					'top': $('.side-wrapper').offset().top + 'px',
					'bottom': 'auto'
				});
			} 
			// check if rectbtfad (bottom most element) has reached the bottom
			if ( ($(window).scrollTop() + $(window).height()) > rectbtfadPos && $('.side-wrapper').css('position') != 'fixed' ) {
				$('.side-wrapper').css({
					'width': $('.col-2').width(),
					'position': 'fixed',
					'bottom': '0',
					'top': 'auto'
				});
			}
		}
		// set last scroll position to determine if scrolling up or down
		lastScrollPos = $(window).scrollTop();

	});
}

Some notes:

  • .rectbtfad is the bottom most element in my sidebar
  • I'm using the height of my #masthead because it's a sticky header so it needs to compensate for it
  • There's a check for col-2 float since I'm using a responsive design and don't want this activating on smaller screens

If anyone can refine this a bit more that'd be great.

Solution 8 - Jquery

function fixMe(id) {
	var e = $(id);
	var lastScrollTop = 0;
	var firstOffset = e.offset().top;
	var lastA = e.offset().top;
	var isFixed = false;
	$(window).scroll(function(event){
		if (isFixed) {
			return;
		}
		var a = e.offset().top;
		var b = e.height();
		var c = $(window).height();
		var d = $(window).scrollTop();
		if (b <= c - a) {
			e.css({position: "fixed"});
			isFixed = true;
			return;
		}			
		if (d > lastScrollTop){ // scroll down
		    if (e.css("position") != "fixed" && c + d >= a + b) {
				e.css({position: "fixed", bottom: 0, top: "auto"});
			}
			if (a - d >= firstOffset) {
				e.css({position: "absolute", bottom: "auto", top: lastA});
			}
		} else { // scroll up
			if (a - d >= firstOffset) {
				if (e.css("position") != "fixed") {
					e.css({position: "fixed", bottom: "auto", top: firstOffset});
				}
			} else {
				if (e.css("position") != "absolute") {
					e.css({position: "absolute", bottom: "auto", top: lastA});
				}				
			}
		}
		lastScrollTop = d;
		lastA = a;
	});
}

fixMe("#stick");

Working Example: https://jsfiddle.net/L7xoopst/6/

Solution 9 - Jquery

There is a relatively unknown plugin in Wordpress repository known as WP Sticky Sidebar. The plugin does exactly what you wanted (Sticky sidebar: stick to bottom when scrolling down, top when scrolling up) WP Sticky Sidebar Wordpress repository Link: https://wordpress.org/plugins/mystickysidebar/

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionandbamnanView Question on Stackoverflow
Solution 1 - JquerygmoView Answer on Stackoverflow
Solution 2 - JqueryTravis KripleanView Answer on Stackoverflow
Solution 3 - JqueryAnoop NaikView Answer on Stackoverflow
Solution 4 - JqueryKrzysztof ANView Answer on Stackoverflow
Solution 5 - JqueryRishi MalviyaView Answer on Stackoverflow
Solution 6 - JqueryJakePowellView Answer on Stackoverflow
Solution 7 - JquerycallmeforsoxView Answer on Stackoverflow
Solution 8 - JquerySezginOnlineView Answer on Stackoverflow
Solution 9 - JqueryManju TalluriView Answer on Stackoverflow