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 NaN - not 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.

Related screencasts

Demo

If you find this demo doesn't work as expected, it's possibly due to the demo running from within an iframe. Try running the demo in it's own window.

Source Code

Comments

  1. James Wong On 2nd January 2010 at 08:01

    Hello there, nice post but I got a hitch in here. What if I have another element underneath ‘commentWrapper’ and I want the box to stop right on before it.

  2. Nicolas Chevallier On 6th January 2010 at 19:01

    Thanks for this piece of code. I add this to my toolbox for the project I work on. It’s simple but I never see a smooth scroll like yours, without any flickering problem.

  3. John-David Dalton On 26th January 2010 at 17:01

    The whole parseFloat($('#comment').css('margin-top').replace(...)) is way to over complicated. NaN values will convert to boolean false so this will work parseFloat($('#comment').css('margin-top')) || 0;.

  4. eriadam On 19th February 2010 at 12:02

    Hi Remy, thanks for the j4d podcast, I love it! I just discovered that in Firefox 3.6 (only Windows) the scroll event is not being triggered while I’m actually scrolling, just only when I release the sidebar or slow the scrolling down. The y variable looks like this:
    y = 0
    y = 312
    y = 772
    y = 954
    y = 958
    And as it is not continuous (like 5-6 step at the time) it is jumping. Mouse wheel scrolling is the same: while the page is moving, the event is not triggered. Do you have any idea how to fix this?

  5. Zander On 3rd March 2010 at 12:03

    This is brilliant! I used it recently on the Prometheus Bound Books website, but I am building a new site and would really like to have the same functionality for horizontal scrolling rather than vertical. Can this be done?

    Many thanks Remy for creating this tuorial and all the others.

  6. Martin Berglund On 7th March 2010 at 17:03

    I have noticed what I think is a browser related bug… :-/

    If the technique in this tutorial is used on a fixed width, centered layout, the fixed element will not stay in its x-position when the browser window is resized. In the Apple Store it seems like Apple have found a solution that does not break when resizing the browser window.

    Firefox plays nice, but Safari is not as cool about this. If ‘position: fixed’ is not added through jQuery, Safari plays nice too.

    I am terrible at describing code and website behaviour, but I hope you understand what I am trying to explain.

    You can see a demo of the bug here (centered, fixed width): http://jsbin.com/aqeji

  7. Jason On 11th March 2010 at 08:03

    Hmm, only problem is if the contents of that floating elements are longer than the height of the browser window, there is no way to ever get to the bottom of the floating element. Open it in a new window, make the browser 200px tall or something and then the button is not possible to get to. So this will be a problem on phones, etc. Should be some way to add this functionality, but I’ll look into it when it becomes a priority. Ah, looking at Apple’s just gave me the quick fix idea of if the floating element height is at any point larger than the browser height at that time, just don’t apply the effect. If anyone wants to come up with the better solution of having it scroll to the bottom of the too tall floating contents and then stop the effect (until it goes back up), that would be sweet.

  8. Jason On 11th March 2010 at 08:03

    Oh, quick fix just popped into my head after I looked at Apple’s. If the height of the floating element is taller (will need to be rechecked upon any client or ajax changes or browser resize) than the browser window width, just don’t apply the effect. Handling it better will take more time and feel free to implement it for me, anyone. :)

  9. Golioth On 23rd March 2010 at 17:03

    How about creating something James Wong, and earlier Thorsten too, suggested: switch the box from fixed back to an absolute position on reaching the bottom of a page or the top of the next element, for example a footer.

    Anybody know how to achieve this? Like when reaching a given height from the bottom of the page.

  10. Ricardo On 29th March 2010 at 16:03

    Like Jason wrote before, i have a question, what if the floating element is bigger than the viewport?, well, maybe there can be a easy possible solution for this, but anyways, my real question is: what if the viewport is not very big OR what if there is a footer in the end? Is there any ways to make the floating element change state when its near the en of its container. In Apple’s page when you get to the end of the page, the floating element changes it state and stops floating. I dont know if you can understand, but if you go to the Apple store, and make the viewport smaller you can see what I mean. Please any help would be very appreciated.

  11. Johan van de Merwe On 14th April 2010 at 21:04

    Great work! Am putting it into a site I am working on and it is soooooooooooooooo smooth. Thank you!

    JJ from Bad Salzuflen, Germany

Comments are now closed.