Search

Fixed Floating Elements

Posted on 23rd October 2009 — On visiting Apple’s web site an putting items in my shopping basket, I noticed (an old effect) where the shopping basket would follow me down the page. We’ll look at how to replicate the fixed floating sidebars or elements with very little jQuery.

Watch

Watch jQuery Fixed Floating Elements screencast (Alternative flash version)

QuickTime version is approximately 45Mb, flash version is streaming.

View the demo used in the screencast

Understanding the effect

When I scrolled down the Apple store when I had something in my basket I found that the summary and what I had selected would follow me down the page. I’ve seen this effect before, but often it would be a little jumpy and the element would have to catch up with the scrolling.

Apple’s version was very smooth and didn’t jump when I kept scrolling.

Example of the scrolling fixed floating sidebar

Upon firing up Firebug I can see that once the scrollbar gets to the point where the basket is at the top of the page, the basket has a class applied to it which gives it a position: fixed – which explains why it holds still while I continue to scroll down.

Other ways to do it

Like I said, I’ve seen this effect before, for example on Simon Willison’s web site (see the comments form) (one of the speakers for my Full Frontal conference).

When I scroll down the page, the comments form can sometimes “play catchup” with the scrolling position, because I suspect the top position is being recalculated (though I’ve not checked his code to be sure or not).

So I’ve taken the style of Simon’s site for the example and added the jQuery we need to make this a nice smooth effect.

Caveat: IE6

Since we’re solving this effect using position: fixed, IE6 doesn’t support this CSS property. I’m not saying that IE6 doesn’t matter, but I’m suggesting that this effect isn’t a requirement to be able to interact with the site properly, so if IE6 users don’t see this extra effect, I’m okay with this. As I explained in the screencast, you’ll need to decide this yourself, check your site’s demographic, whether it’s a personal project, etc.

Markup & CSS

The trick really happens in the CSS here and being able to flip back and forth between absolute positioning and fixed positioning. So I’ve prepared the layout as such. You guys and gals being designer and front-end types will know how you’ll want to style the elements.

One trick I did find was that I had to wrap the element that would receive position: fixed in a wrapper with position: absolute so that the left position was again the wrapper rather than the body element. The effect of not having the wrapper, meant that when I switched fixed on the element, it jumped to the left by the amount of padding and margin set on the body element.

I’ve simplified the markup form the live example

<style>
/* required to avoid jumping */
#commentWrapper {
  left: 450px;
  position: absolute;
  margin-left: 35px;
  width: 280px;
}

#comment {
  position: absolute;
  top: 0;
  /* just used to show how to include the margin in the effect */
  margin-top: 20px;
  border-top: 1px solid purple;
  padding-top: 19px;
}

#comment.fixed {
  position: fixed;
  top: 0;
}
</style>

<div id="comments">
  <ol>
    <li>Here be the comments from visitors...</li>
    <li>etc...</li>
  </ol>
</div>

<div id="commentWrapper">
  <div id="comment">
    <form>
      <!-- take their response -->
    </form>
  </div>
</div>

jQuery

The jQuery required is very simple to create this effect. Obviously first up, include jQuery:

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.js"></script>

First of all, we need to capture the initial position of the comments form. There’s a few parts to this to ensure we get a real number (rather than NaNnot a number), and to ensure we also calculate in the margin-top:

var top = $('#comment').offset().top;

Next we subtract the margin-top (I’ve used marginTop in the code below, but they’re interchangeable) of our element:

var top = $('#comment').offset().top - $('#comment').css('marginTop');

However, the result from the CSS method is “20px”, so we need this as a number so we can subtract properly, we’ll use parseFloat to achieve this:

var top = $('#comment').offset().top - parseFloat($('#comment').css('marginTop'));

Finally, if we don’t include a margin in the CSS, the result of the CSS method call is auto, which won’t parse properly, so we need to replace the text “auto” with the number 0:

var top = $('#comment').offset().top - parseFloat($('#comment').css('marginTop').replace(/auto/,0));

This all needs to be done inside once the document has loaded, so we’ll wrap all of this in the ready method, and while we’re at it we need to bind an event handle when the user scrolls down the page, so we’ll attach this when the page has loaded:

$(document).ready(function () {
  var top = $('#comment').offset().top - parseFloat($('#comment').css('marginTop').replace(/auto/,0));

  $(window).scroll(function () {
    // let's do something funky
  });
})

Within the scroll event handler, we need to:

  1. Capture the current scrolled Y position using $(window).scrollTop()
  2. If the comment element is above the Y position, add the fixed class
  3. Otherwise remove the class

Note that since we bind the scroll event to the window object, I’ve used $(this).scrollTop() (since this is also the window in this case).

All of this put together results in:

$(document).ready(function () {
  var top = $('#comment').offset().top - parseFloat($('#comment').css('marginTop').replace(/auto/, 0));
  $(window).scroll(function (event) {
    // what the y position of the scroll is
    var y = $(this).scrollTop();

    // whether that's below the form
    if (y >= top) {
      // if so, ad the fixed class
      $('#comment').addClass('fixed');
    } else {
      // otherwise remove it
      $('#comment').removeClass('fixed');
    }
  });
});

And that’s how to create a fixed floating element or sidebar and make it super smooth when you scroll.

There is some optimisation you could do to this code, but for a simple effect this should work nicely for you.