Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at the jQuery Conference 2009 (Cambridge, MA) with:

Creating A Sometimes-Fixed-Position Element With jQuery

By Ben Nadel on

The other day, I was on a website that had a menu bar at the top. When I scrolled down the page, below the point of the menu bar, suddenly, the menu bar poped back into view, this time stuck to the top of the window. When I scrolled back up the page, the menu bar fell back into its original document flow. I thought this was a cool effect I wanted to see if it was something that I could accomplish with jQuery (NOTE: With the power of jQuery, I am no longer afraid to try things, but rather ferociously curious to see just how much I can accomplish).

 
 
 
 
 
 
 
 
 
 

After playing around with this effect, I came up with two different approachs: In one approach, an element which is originally part of the document flow, is pulled out of the flow and given a fixed position when appropriate. In the second approach, an element that starts with an absolute position and is never part of the document flow, is given a fixed position when appropriate. From my cursory experimentation, each of these approaches seems to have its own pros and cons.

In the first demo, we'll examine the first approach in which the "fixed" element starts off as a static element contained within the document flow (NOTE: By "document flow", I am referring to the concept in which an element's dimentions effect the layout of the surrounding nodes). The complication with this approach is that when we change an element's position to "fixed," it is no longer part of the document flow and the elements below it slide up to take its place. As such, we need a way for a fixed-position element to maintain its original document-flow-dimentions. To do so, I am using a parent node placeholder with a calculated height:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Changing To Fixed Position When Window Scrolls</title>
  • <style type="text/css">
  •  
  • body {
  • margin: 0px 0px 0px 0px ;
  • padding: 0px 0px 0px 0px ;
  • }
  •  
  • #site-title {
  • margin: 0px 0px 0px 0px ;
  • padding: 10px 20px 10px 20px ;
  • }
  •  
  • #site-message {
  • background-color: #F0F0F0 ;
  • width: 100% ;
  • }
  •  
  • #site-message span {
  • display: block ;
  • padding: 10px 20px 10px 20px ;
  • }
  •  
  • div.site-message-fixed {
  • border-bottom: 1px solid #E0E0E0 ;
  • left: 0px ;
  • position: fixed ;
  • top: 0px ;
  • }
  •  
  • #site-body {
  • padding: 20px 20px 500px 20px ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="jquery-1.4a2.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize the scripts.
  • jQuery(function( $ ){
  •  
  • // Get a reference to the placeholder. This element
  • // will take up visual space when the message is
  • // moved into a fixed position.
  • var placeholder = $( "#site-message-placeholder" );
  •  
  • // Get a reference to the message whose position
  • // we want to "fix" on window-scroll.
  • var message = $( "#site-message" );
  •  
  • // Get a reference to the window object; we will use
  • // this several time, so cache the jQuery wrapper.
  • var view = $( window );
  •  
  •  
  • // Bind to the window scroll and resize events.
  • // Remember, resizing can also change the scroll
  • // of the page.
  • view.bind(
  • "scroll resize",
  • function(){
  • // Get the current offset of the placeholder.
  • // Since the message might be in fixed
  • // position, it is the plcaeholder that will
  • // give us reliable offsets.
  • var placeholderTop = placeholder.offset().top;
  •  
  • // Get the current scroll of the window.
  • var viewTop = view.scrollTop();
  •  
  • // Check to see if the view had scroll down
  • // past the top of the placeholder AND that
  • // the message is not yet fixed.
  • if (
  • (viewTop > placeholderTop) &&
  • !message.is( ".site-message-fixed" )
  • ){
  •  
  • // The message needs to be fixed. Before
  • // we change its positon, we need to re-
  • // adjust the placeholder height to keep
  • // the same space as the message.
  • //
  • // NOTE: All we're doing here is going
  • // from auto height to explicit height.
  • placeholder.height(
  • placeholder.height()
  • );
  •  
  • // Make the message fixed.
  • message.addClass( "site-message-fixed" );
  •  
  • // Check to see if the view has scroll back up
  • // above the message AND that the message is
  • // currently fixed.
  • } else if (
  • (viewTop <= placeholderTop) &&
  • message.is( ".site-message-fixed" )
  • ){
  •  
  • // Make the placeholder height auto again.
  • placeholder.css( "height", "auto" );
  •  
  • // Remove the fixed position class on the
  • // message. This will pop it back into its
  • // static position.
  • message.removeClass( "site-message-fixed" );
  •  
  • }
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <div id="site-header">
  •  
  • <h1 id="site-title">
  • Changing To Fixed Position When Window Scrolls
  • </h1>
  •  
  • <!--
  • The placeholder will keep the visual space of the
  • message taken up when the message is placed in a
  • fixed position. This will prevent the content from
  • sliding up.
  • -->
  • <div id="site-message-placeholder">
  •  
  • <div id="site-message">
  • <span>
  • Welcome to my website! Thanks for visiting!
  • </span>
  • </div>
  •  
  • </div>
  •  
  • </div>
  •  
  • <div id="site-body">
  •  
  • <!--
  • I'm using Javascript here just to fill up the
  • page with content so that the page will scroll
  • when I demo it.
  • -->
  • <script type="text/javascript">
  •  
  • for (var i = 0 ; ++i < 20 ;){
  •  
  • document.write(
  • "<p>" +
  • "Here is some content to help make sure" +
  • "the page will scroll a bit for the" +
  • "demonstration video." +
  • "</p>"
  • );
  •  
  • }
  •  
  • </script>
  •  
  • </div>
  •  
  • </body>
  • </html>

As you can see in the above code, my "fixed" element has a placeholder parent. As the window scrolls and I need to actually set the position of the target element to, "fixed," I first set the height of the parent placeholder to explicitly be its own height. This way, even when the target element is taken out of the document flow, the placeholder will prevent the succeeding content from sliding up.

The benefit of this approach is that you can start with a relatively natural document flow. The downside to this approach is that the calculations require you to alter multiple elements rather than the target element alone.

In the second demo, we'll examine the second approach in which the "fixed" element starts off as an absolutely positioned element that is never part of the document flow. Because absolutely positioned elements do not affect the layout of the elements around them in the markup, we need to artificially take up that space:

  • <!DOCTYPE HTML>
  • <html>
  • <head>
  • <title>Changing To Fixed Position When Window Scrolls</title>
  • <style type="text/css">
  •  
  • body {
  • margin: 0px 0px 0px 0px ;
  • padding: 0px 0px 0px 0px ;
  • }
  •  
  • #site-header {
  • height: 90px ;
  • }
  •  
  • #site-title {
  • margin: 0px 0px 0px 0px ;
  • padding: 10px 20px 10px 20px ;
  • }
  •  
  • #site-message {
  • background-color: #F0F0F0 ;
  • width: 100% ;
  • }
  •  
  • #site-message span {
  • display: block ;
  • padding: 10px 20px 10px 20px ;
  • }
  •  
  • div.site-message-absolute {
  • left: 0px ;
  • position: absolute ;
  • top: 60px ;
  • }
  •  
  • div.site-message-fixed {
  • border-bottom: 1px solid #E0E0E0 ;
  • left: 0px ;
  • position: fixed ;
  • top: 0px ;
  • }
  •  
  • #site-body {
  • padding: 20px 20px 500px 20px ;
  • }
  •  
  • </style>
  • <script type="text/javascript" src="jquery-1.4a2.js"></script>
  • <script type="text/javascript">
  •  
  • // When the DOM is ready, initialize the scripts.
  • jQuery(function( $ ){
  •  
  • // Get a reference to the message whose position
  • // we want to "fix" on window-scroll.
  • var message = $( "#site-message" );
  •  
  • // Get the origional position of the message; we will
  • // need this to compare to the view scroll for
  • // reverting back to the original display position.
  • var originalMessageTop = message.offset().top;
  •  
  • // Get a reference to the window object; we will use
  • // this several time, so cache the jQuery wrapper.
  • var view = $( window );
  •  
  •  
  • // Bind to the window scroll and resize events.
  • // Remember, resizing can also change the scroll
  • // of the page.
  • view.bind(
  • "scroll resize",
  • function(){
  •  
  • // Get the current scroll of the window.
  • var viewTop = view.scrollTop();
  •  
  • // Check to see if the view had scroll down
  • // past the top of the original message top
  • // AND that the message is not yet fixed.
  • if (
  • (viewTop > originalMessageTop) &&
  • !message.is( ".site-message-fixed" )
  • ){
  •  
  • // Toggle the message classes.
  • message
  • .removeClass( "site-message-absolute" )
  • .addClass( "site-message-fixed" )
  • ;
  •  
  • // Check to see if the view has scroll back up
  • // above the message AND that the message is
  • // currently fixed.
  • } else if (
  • (viewTop <= originalMessageTop) &&
  • message.is( ".site-message-fixed" )
  • ){
  •  
  • // Toggle the message classes.
  • message
  • .removeClass( "site-message-fixed" )
  • .addClass( "site-message-absolute" )
  • ;
  •  
  • }
  • }
  • );
  •  
  • });
  •  
  • </script>
  • </head>
  • <body>
  •  
  • <div id="site-header">
  •  
  • <h1 id="site-title">
  • Changing To Fixed Position When Window Scrolls
  • </h1>
  •  
  • </div>
  •  
  • <!--
  • The message will start out with an absolute position;
  • then, it will be switched to a fixed position late.
  • NOTE: At no point is this element actually part of the
  • document flow.
  • -->
  • <div id="site-message" class="site-message-absolute">
  • <span>
  • Welcome to my website! Thanks for visiting!
  • </span>
  • </div>
  •  
  • <div id="site-body">
  •  
  • <!--
  • I'm using Javascript here just to fill up the
  • page with content so that the page will scroll
  • when I demo it.
  • -->
  • <script type="text/javascript">
  •  
  • for (var i = 0 ; ++i < 20 ;){
  •  
  • document.write(
  • "<p>" +
  • "Here is some content to help make sure" +
  • "the page will scroll a bit for the" +
  • "demonstration video." +
  • "</p>"
  • );
  •  
  • }
  •  
  • </script>
  •  
  • </div>
  •  
  • </body>
  • </html>

As you can see in the above code, the "site header" element has an explicit height; this height pushes down the content and makes visual room for the absolutely positioned element. The benenfit to this approach is that you only have to deal with the target element, swapping its classes as necessary. The downside to this approach is that your document has to be defined in such a way that it makes visual room for an element that is not really part of the document flow.

This was just a quick exploration of the sometimes-fixed element effect; in a real world sceneario, there's likely a lot more to consider, such as how to deal with content that is centered on the page. But for now, it's nice to see that the mechanics behind the magic are fairly straightforward.



Reader Comments

I like the approach where the element starts with a position: static (rendered in the normal flow of the document) and when the page is scrolled away from the top (or past the element's top offset), the element gets position: fixed.

Of course, that doesn't work in IE6.. so you have to test for that (preferably testing if the browser supports position: fixed) and in that case, use position: absolute with the top value being set on $(window).scroll.

Reply to this Comment

@Cowboy,

Good point - I should have mentioned that this doesn't work in IE6 (in either case). I also like the one where it starts out "static" because I think that allows the document to be developed more naturally.

Reply to this Comment

Curious if the site you were looking at with this feature used jQuery as well to create the effect or if it was done some other way?

Reply to this Comment

@John,

Good question; I just looked at the source code and it looks like they are loading Prototype and Scriptaculous.

Reply to this Comment

I almost wanna say that this can be done with a div and CSS....YES! Just looked it up. The operative CSS code is 'position:fixed.' One of my favorite things to do with Excel is to freeze the header row so that it is always visible. I recorded a macro and set it to a button for this reason It's something I like to do with my HTML pages as well. The title and menu bar stay visible while the rest of the offset body scrolls up under it.

My initial questions was, "Other than exercising your jQuery muscles, what makes this any different?" Then I saw that the point of the exercise is to 'pop' the menu to the top of the window and then 'un-pop' it when the header comes back into view.

I freaking LOVE jQuery!! (:

Reply to this Comment

@Kristopher,

This approach is actually using "position: fixed" as the way to keep the menu in frame at all times. The difference here, though, is that the element does not start off as position: fixed. This allows the placement of it to be determined by the state of the page.

Reply to this Comment

@Cowboy,

I can't come up with a way to programmatically test for "fixed" position support. Everything I try fails because the BODY is not yet available at the time the test would need to take place.

Can you think of anyway to do this? Or do we basically just have to fall back on browser sniffing for this particular feature?

Reply to this Comment

Ben.. I've always wanted to do this in my website but I had do this with tables, this way it's much simpler. Really nice. Thanks for your post.

Reply to this Comment

GOOD?
But I think some places do not need jquery?

like this:
<pre>
var originalMessageTop = (message=document.getElementById("site-message")).getBoundingClientRect().top;
function fix() {
var viewTop = this.pageYOffset||document.documentElement[ "scrollTop" ];
message.className= viewTop > originalMessageTop? "site-message-fixed":"site-message-absolute";
}
this.addEventListener("scroll",fix,0);
</pre>

Reply to this Comment

@Gabriela,

No problem :)

@Caii,

The getElementById() and logical ORs you are doing is exactly the kind of stuff jQuery hides. Plus, if you plan to have more Javascript on the page, it's almost silly not to leverage the library (in my experience).

@Nicolas,

Cool man, I'll take a look.

Reply to this Comment

Ben... I haven't even read past the first paragraph, and I was stopped cold by this line:

With the power of jQuery, I am no longer afraid to try things, but rather ferociously curious to see just how much I can accomplish

Ain't that the truth, Ruth!

I'm not even a real coder like you... I'm a designer who started doing XHTML/CSS out of necessity, and just started to pick up a bit of javascript & jQuery in the last year. It's got me totally psyched to try new things, and every day at work I try to convince my bosses into letting me try new things just so I can play with jQuery.

Anyway, thanks for the near-constant source of enthusiasm and evangelism. Now back to the actual article/video...

Cheers!

Reply to this Comment

@Lelando,

That's awesome! Glad you're liking jQuery as much as I am. I think it's awesome that you started out on the front end - I think that will give you an edge up on creating better software.

Reply to this Comment

Ben,

This could not have come at a more perfect time! I'm just beginning a redesign for the company I work for and we're looking to do something almost exactly like this, but instead of something from the header being static, it'd be something from the footer.

I've been searching all day for something, anything, that would get me started. I couldn't even come up with a name for what I want to do, and I finally hit upon the right combination of keywords for almighty Google to send me here.

Anyhow, I'm going to be bookmarking, emailing links to several of my accounts, and making a shortcut on my flash drive, just so I don't lose this one. :D

Thanks!!

Reply to this Comment

Thank you very much for your time. This is what I was looking for. You'll see a live example on my website soon :)

Thanks again,
Ciprian

Reply to this Comment

This is very cool. Thanks for demo. However, I can't seem to get this to work in any version of IE (6 thru 8).

Reply to this Comment

@Harman,

Hmm. I don't often run IE in quirksmode. I would suggest using a stricter doctype to get better box-model support cross-browser.

@Matt,

Are you using a doctype? As @Harman pointed out, if you are running in quirksmode, this might not be functioning properly.

Reply to this Comment

Good job Ben, looks pretty nice.

Using this approach, would I be able to have the same effect, but on bottom, instead of the top?

Reply to this Comment

@Bruno,

You should be able to adapt the same thing to the bottom. The only issue is that, when at the top, there is this sense of initial, static position that switches over to fixed. I am not sure what would be the best visual experience parallel on the bottom.

Would you start with an initial fixed position? And then, convert to a static position when the user scrolls down enough?

Reply to this Comment

@Tim,

It's funny - I totally hadn't noticed that the rest of the page was fixed until you pointed it out. Perhaps they just always have it fixed? Since those elements (the top bar, left bar) are always there, I suppose there's no benefit to ever having them not be fixed?

Reply to this Comment

Tim - I assume you got me totally wrong, and re-reading my comment I now see I could have been a lot clearer.

Your first example makes an element in the regular document flow stick to a fixed position when a certain position relative to the document window/another element is reached. The "new" google image search (http://googleblog.blogspot.com/2010/07/ooh-ahh-google-images-presents-nicer.html) features a lot of "lazy-loading" image results on a single page, which are loaded on demand as you scroll down. For easier orientation, the image results are grouped into "pages" of 20 images. Each 20 images so have a headline ("Page 1"). As you scroll down the image results, the headline of the "current page" sticks to the top of the search results - which is the effect I was talking about.

Sorry for being so undescriptive the first time around.

Reply to this Comment

Ben. Thanks for the information. Always glad when I see your site in a Google search -- means the solution will be short and sweet. This is no exception. Thanks again.

Reply to this Comment

this might be a solution for IE6 fix add this line of css:
*html div.site-message-fixed {position: absolute;top: expression((document.documentElement.scrollTop || document.body.scrollTop) + this.offsetHeight - this.offsetHeight)}

this works fine on my website

Reply to this Comment

@Romain,

The "expression" stuff is pretty cool. I've played around with this approach. I wish it was a bit smoother in its updates; but I find it to be a bit jumpy. But, considering that it's for IE6... it's probably not worth worrying about :)

Reply to this Comment

Hi Ben,

Really nice script but i m having problem in this script. its not running in my code its not in bind function's second parameter function what to do?
can you help me?

Reply to this Comment

First I want to thank you for posting this up, I was looking for a way to do this forever!

My question is: I'm trying to attach a drop-down sub-menu to my "#site message" (in method #1). The drop-down is triggered by clicking a link in the "#site message" main menu. But whenever the event fires it bumps the page back up to the top.

I've been trying to figure out a solution using .position() or .offset() but functions where I try to retrieve these values for "#site message" renders the original code moot (ie, "#site message" is static on the page).

Any ideas about a different direction I could take with this? Thanks again :D

Reply to this Comment

Hey Ben,

Love this approach. Simple, straightforward, and effective. Someday I hope I can come up with something this clever myself. Until then, thanks for your tuotrials!

Reply to this Comment

Thank a lot.

I tried to find a way to solve this problem for 3 days. After reading this article. All problems were solved. It is an easy way. And practical.

Reply to this Comment

this was almost what i was looking for ben, but what if you have multiple boxes you wanted to add this effect to? say each section of sectioned off by something like you had, and when the next section comes up, the previous one goes back to unfixed, and the new one takes it's place. something like this...

http://www.girlfriendnyc.com/#/portfolio

Reply to this Comment

Hi..
Thanks for sharing this information and resources it's really help full for me with the help of this we can improve our designs and development I really inspire with this information thanks

Reply to this Comment

Hello, nice script but i am trying to get it working on wordpress, sidebars and nothing done! can i use it or i am wasting my time?

Please tell me. Thank you.

Reply to this Comment

Thank you very much. This is the second time that I found you and I couldn't be happier. The other one was about "flickr style image tagging".

Reply to this Comment

With just that I can create the same effect:

$(document).ready(function() {
$("#topBar").css("position", "absolute");
});

$(window).scroll(function() {
$("#topBar").css("top", $(window).scrollTop() + "px");
});

Reply to this Comment

Oh man, that's a really cool thing. I have been looking for something like this for awhile...I just forgot. I'm posting a comment mainly so I can keep it in my inbox and kind of "bookmark" it for later when I have time to try to do this. Thanks for this post! It's awesome.

Reply to this Comment

I much prefer the first method and it works perfectly for my site design. Thanks a lot for this. It really helped me out!

Reply to this Comment

great work.. thanx for providing souce... as a designer it is really useful to me. i am following your website.. keep posting

Reply to this Comment

Hello Ben,

first of all great Script! But there is a little bug with Android Browsers... The Bar is moving but it seems there is a bug with the fixed positioning. On my tablet the bar moves around 100px to the right when i scroll. Can i solve it somehow?

Greets
Arti

Reply to this Comment

Hi Ben,

Great script and thanks for sharing it. I have implemented it into my site and changed it to a fixed width, ie changed body, site-header, site-message and site-body to a fixed width in pixels.

All works as it should apart from this one part which I can't fix.

If you magnify when the vertical scroll is at the top until the horizontal scroll bars appear the top moves horizontally in line with the body when you use the horizontally scroll as it should.

But if you scroll down from the top then magnify until the horizontal scroll bars appear and then use the horizontally scroll the top does not move.

In brief change to fixed width and magnify when vertical scroll at top all is good but if scroll off vertical then magnify the top does not scroll.

Appreciate I've changed it from your demo but if you or anyone can help it would be much appreciated.

Many thanks
Steve

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.