Skip to main content
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Dan Skaggs
Ben Nadel at NCDevCon 2016 (Raleigh, NC) with: Dan Skaggs

Creating A Sometimes-Fixed-Position Element With jQuery

By
Published in Comments (52)

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.

Want to use code from this post? Check out the license.

Reader Comments

19 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.

15,902 Comments

@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.

17 Comments

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!! (:

15,902 Comments

@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.

15,902 Comments

@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?

1 Comments

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>

15,902 Comments

@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.

2 Comments

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!

15,902 Comments

@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.

1 Comments

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!!

1 Comments

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

1 Comments

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).

15,902 Comments

@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.

1 Comments

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?

15,902 Comments

@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?

15,902 Comments

@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?

2 Comments

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.

1 Comments

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.

1 Comments

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

15,902 Comments

@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 :)

1 Comments

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?

1 Comments

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

2 Comments

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!

1 Comments

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.

1 Comments

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

2 Comments

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

1 Comments

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.

1 Comments

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".

1 Comments

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");
});

383 Comments

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.

1 Comments

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

1 Comments

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

1 Comments

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

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel