Using jQuery to create a scrolling sidebar

The easiest way to start this post is with an image explaining the concept.

layout

So now as you can see we have a basic site layout a 2 column with a fixed header and footer. For this example I’m dealing with a fixed header & footer for simplicity but you’ll see you can easily handle dynamic sizing also. The content column is a dynamic size column that can contain a large amount of text that if you scroll down past the fold of the page you will lose track of the sidebar. So the goal I was seeking was a way to have the sidebar follow the scroll bar in an aesthetically pleasing manner.

At first I attempted a fixed width layout but then ran into all of the browser compatibility issues so I turned to jQuery. The first thing we need to achieve is to make the sidebar track the scrollbar.

<script type="text/javascript">
$(window).scroll(function() {
    $('#sidebarPage1').animate(
        { top: $(window).scrollTop() + 'px' }
    );
});
</script>

This will achieve the tracking we desire however it is a very jerky and unpleasant effect so lets see what we can do to tune it up.

<script type="text/javascript">
$(window).scroll(function() {
    $('#sidebarPage1').animate(
        { top: $(window).scrollTop() + 'px' }, 
        { queue: false, duration: 500, easing: 'easeInOutSine' }
    );
});
</script>

This will produce a much nicer effect. The queue parameter we included is to allow jQuery to create a series of events that happen in a certain order to create animation. We don’t want our animation waiting for anything so we set queue: false to make it run immediately. The duration parameter is how long in milliseconds the animation to take, so setting 500 is one half of a second. This lowers the amount of jumping the sidebar does significantly. Lastly the easing parameter allows us to setup an easing algorithmthat influences how the animation handles the start and ends of the effects. If you look at the easing plugin’s site you can play around with the different options that are built into jQuery natively. I chose the easeInOutSine as it seemed to produce a very nice animation where it spends more of the animation time at the start and end where jerkiness can be more easily noticed, but this was just a personal preference.

Now the real fun, polishing this and fixing the “quirks” this code creates. If you make your browser view port very small you can actually scroll the sidebar off the entire screen because of the footer taking up space at the bottom and the sidebar is already offset from the top because it’s below the header. The first is to get it to stop scrolling off the page. This will involve doing some math to calculate limits on the scrolling.

<script type="text/javascript">
    $(window).scroll(function() {
        var dynamicSidebarHeight = $('.sidebar').height();

        if ($(window).height() > dynamicSidebarHeight) {
            var fixedFooterOffset = 150;
            var scrollTo = $(window).scrollTop()
            var calculatedMaxTop = $('#footer').offset().top - dynamicSidebarHeight - fixedFooterOffset;
            var fixedHeaderOffset = 150;

            if (scrollTo > calculatedMaxTop) {
                scrollTo = calculatedMaxTop;
            }

            $('#sidebarPage1')
            .animate(
                { top: scrollTo + 'px' },
                { queue: false, duration: 500, easing: 'easeInOutSine' }
            );
        }
    });
</script>

To explain what each of these variables mean:

  • dynamicSidebarHeight – this is the jQuery calculated height at run time of the sidebar
  • fixedFooterOffset and fixedHeaderOffset – are the height in pixels of the static headers and footers, which for this example are said to be 150px.
  • calculatedMaxTop – This is the equation to know the farthest down the sidebar’s top can be set to that it won’t crash into the footer.

The first check is make sure the browser viewport is atleast as big as the sidebar. If it’s smaller than the sidebar scrolling is pointless because you will never be able to see the bottom of the sidebar because when you’d scroll down to see the rest of the sidebar it will move off screen. After that we calculate the Max value for the Top of the sidebar which is the based off the location of the top of the footer which is what calling .offset().top returns, let’s say 2000px for this example. We’ll assume the sidebar height is 500px and we have our fixed footer height of 150px.

So top of footer is at 2000px we subtract the height of the bar 500px and the height of the footer 150px so in this example the furthest you could set the top of the sidebar would be 1350px since if the top of the sidebar is 1350px, the next 500px is the sidebar and the next 150px is the footer. So we want to check if scrollTo is > than this value and if it is we want it to set it to the maximum length we would allow.

The final part is to handle once we scroll past the page fold the sidebar is now 150px offset from the top of the window due to the header being at the top that has scrolled up off the screen so it would look alot better if the bar stays stuck at the top edge. This is the reason I have the unused fixedHeaderOffset so far which accounts for the height of the header. The final code we will have is this

<script type="text/javascript">
    $(window).scroll(function() {
        var dynamicSidebarHeight = $('#sidebar').height();

        if ($(window).height() > dynamicSidebarHeight) {
            var fixedFooterOffset = 150;
            var scrollTo = $(window).scrollTop()
            var calculatedMaxTop = $('#footer').offset().top - dynamicSidebarHeight - fixedFooterOffset;
            var fixedHeaderOffset = 150;

            if (scrollTo > calculatedMaxTop) {
                scrollTo = calculatedMaxTop;
            }
            else if (scrollTo < fixedHeaderOffset) {
                scrollTo = 0;
            }
            else
            {
                scrollTo -= fixedHeaderOffset - 18; //18 is for a top margin
            }

            $('#sidebarPage1')
            .animate(
                { top: scrollTo + 'px' },
                { queue: false, duration: 500, easing: 'easeInOutSine' }
            );
        }
    });
</script>

So far we’ve asserted that the viewport is larger than the sidebar, we now cannot scroll the sidebar under the footer or off the page. To make handle the calculation of where to have it in relation to the top of the page we add

else if (scrollTo < fixedHeaderOffset) {
    scrollTo = 0;
}
else
{
    scrollTo -= fixedHeaderOffset - 18; //18 is for a top margin
}

To our if statement. The first case is we’ve scrolled back up to the top of the page and want the sidebar to sit under the header so scrollTo is < the height of the header. At this point we set scrollTo to 0 (I have a position: relative wrapper around my content and sidebar columns) if you don't have a position: relative tag around the sidebar you would set this to the fixedHeaderOffset and probably some margin say 18px.

Now we have the case where we're scrolling the bar down and we want it to be at the top of the viewport this is the else case. So the condition for this is scrollTo is shorter than the maximum allowed value AND scrollTo is further down than the header. Since the position of scrollTo includes the height of the header we need to subtract that off of the value of scrollTo and then subtract a little more so the sidebar sits 18px below the top of the page. If you don't have the position: relative container around the sidebar this would most likely just be the offset from the top of the page so scrollTo – 18px.

Unfortunately I don't have a demo for this but this should be very easy to test. I'll throw up a zip file with the html in it to let you test it out.