This is some code to get the header of some content area to stay visible at the top of the screen as you scroll through that content. Then go away when you’ve scrolled past that relevant section.
Couple things to know before we get started:
- There are many like it, but this one is mine.
- This doesn’t work (yet) on mobile (at least mobile Safari that I looked at). Because I don’t know that much about JavaScript on mobile and how to detect scroll events and scroll position and stuff.
HTML
For the sake of demo purposes, we’ll identify areas we wish to apply persistent headers to via a class name “persist-area” and the header within we indent to be persistent with “persist-header”. These are totally unsemantic class names, but you’d fix that in your own implementation by just knowing your own markup and applying appropriate selectors.
<article class="persist-area">
<h1 class="persist-header">
<!-- stuff and stuff -->
</article>
jQuery JavaScript
This is the plain English of what we are going to do:
- Loop through each persistent area and clone the header. The cloned header remains invisible until we need it.
- Every time the window scrolls, we run some tests.
- If we have scrolled into an area which should have a persistent header, but the header would be hidden, we reveal our cloned header, in a fixed position. All other persistent headers will be hidden.
The whole kit and kaboodle:
function UpdateTableHeaders() {
$(".persist-area").each(function() {
var el = $(this),
offset = el.offset(),
scrollTop = $(window).scrollTop(),
floatingHeader = $(".floatingHeader", this)
if ((scrollTop > offset.top) && (scrollTop < offset.top + el.height())) {
floatingHeader.css({
"visibility": "visible"
});
} else {
floatingHeader.css({
"visibility": "hidden"
});
};
});
}
// DOM Ready
$(function() {
var clonedHeaderRow;
$(".persist-area").each(function() {
clonedHeaderRow = $(".persist-header", this);
clonedHeaderRow
.before(clonedHeaderRow.clone())
.css("width", clonedHeaderRow.width())
.addClass("floatingHeader");
});
$(window)
.scroll(UpdateTableHeaders)
.trigger("scroll");
});
CSS
The only CSS we’re directly changing with JavaScript is the visibility of the persistent header. I feel like that’s pretty acceptable. All other styling is rightfully a part of the class. It’s pretty light, all we need is:
.floatingHeader {
position: fixed;
top: 0;
visibility: hidden;
}
And so…
Use as you will.
Yea techniques like this will not work on mobile due to the limited and still crappy support for fixed positioning. Not to mention that scroll events on mobile are slow due to scrolling algorithms used to accelerate touch movement.
Recalculating scroll position and then placing absolutely on the page would be the only effective way to due this on modern mobile browsers. But its a less than usable experience.
Great post thou!
-B
This should work on iOS5, from the looks of it.
Event timing seems a little off but it does work in iOS5
The way I understood it is, iOS 5 will support fixed positioning with a different vendor prefix. So fixed positioning by default will still be disabled, but with the special vendor prefix, you can override the default.
Sorry, guess I should do my research BEFORE posting. Fixed position is supported by default, it’s the new touch-scrolling overflow property that requires a special prefix:
-webkit-overflow-scrolling: touch;
Chris do you use the example of 3 header, so each time q reaches a new header as above, disappears !
Now if I just use a header, as far as it will continue to appear, how can I determine ?
Dosen’t work on iPad …
“This doesn’t work (yet) on mobile (at least mobile Safari that I looked at).”
It’s working in iOS 5 already :)
There is one thing that I don’t like about position:fixed, is that it fixes also left position. When window’s width is less than fixed header’s width scrolling left will move all page content except the fixed part.
But it is possible to change left position of fixed element on scroll and resize window events. You have to add something like this for each “.persist-area” in UpdateTableHeaders function:
Thanks so much. That was very helpful :)
Really nice, and clever way to do it, thanks a lot :)
Correct me if I am wrong please, but apparently the header will in this case always be positioned relatively to the window as it uses a fixed position right? so If you like to use a parent element and position the header to the element which could only appear in the middle of screen wouldn’t be possible?
Nice post Chris.
One tip is the performance associated with attaching handlers to the scroll event, particularly ones that query the DOM on every single scroll. Much better to cache the DOM references instead of looking them up each time.
Twitter had this issue once and John Resig wrote a helpful post with some alternative JS options – http://ejohn.org/blog/learning-from-twitter/
Learned a lot from this post.
Including the fact that it’s “kit and kaboodle.”
Not kitten kaboodle.
@Chris Coyier, I had used some jquery on an iPad micro site for my company, It detected scroll position and would update the position of a menu bar, I also used it to auto scroll to the top of specific sections based on current position. I’ll get a link to you for that.
Brilliant, thanks a lot. I always wanted to know how to do that.
Very nice! Ive seen this done on iphone apps and thought it was a great technique. Never thought of how to accomplish this via html, js, etc. Thanks!!
Great example Chris.
I’ve done the same lately, check this out:
studio.conduit.com
You should take a look at the iScroll project (http://cubiq.org/iscroll). It was created to solve this problem specifically for iOS devices, but ofcause works on other modern mobile platforms.
The main advantage of iScroll is that it achieves effect my intelligently using CSS. This means web browsers, mobile or otherwise, will use hardware acceleration. This results in a really smooth effect.
You should take a look at the iScroll project (http://cubiq.org/iscroll-4). It was created to solve this problem specifically for iOS devices, but ofcause works on other modern mobile platforms.
The main advantage of iScroll is that it achieves effect my intelligently using CSS. This means web browsers, mobile or otherwise, will use hardware acceleration. This results in a really smooth effect.
you really have very cool stuff to give
Thats a nice usability feature for tablebased content. Hope that in future more sites would like tricks like that. Something to improve the usability on table content could be too to colour every second line different with the :nth-child selector =)
They are OK for table headers, but should never be used for text headings, or for site header, because it will overlap content and will make it unreadable, you have to scroll up to read it. Fixed elements drag too much attention, away from content (I hate these fixed facebook buttons at edge of some sites that follow scrollbar).
There just isn’t enough information here to make a call whether you should use a technique like this or not in an actual design. Like every tutorial ever written, actual usage depends on actual scenarios.
Why are you triggering a scroll event at the end? Wouldnt this be fired anyway since you arent using preventDefault?
I feel like the header should stop 20px or so before the bottom, but that adds a bit of complexity to the issue. It just doesn’t make a ton of sense for the header to be all the way at the bottom of the box, so that absolutely none of the content can be seen. That’s just my opinion though, I’m sure there are cases where I’d want it to work exactly like this too.
Chris, you set as var
clonedRow
, but useclonedHeaderRow
Nice post! =)
Thanks for the post! I’ve been thinking about learning this one for a short while, but hadn’t had time to investigate. Happy it was delivered right to my inbox. Not to mention I’m a long time fan of this website and tuts involved. Thanks again!
This works perfectly on my Asus Transformer.
Thanks! I was just wondering how to do that. :)
DAM! I love this new design…just had to say.
I like the new design too, especially the “floating design”!
Awesome tutorial ! I always wondered how this is done. Hope it works across platforms too ! Thanks Chris.
Nice script, but I cant get it to work good for tables with dynamic width.
The td’s in the cloned header tr wont inherit the column width of the tbody td’s.
Quick&dirty; to illustrate what I’m trying to describe :
try this see if it works:
var $floatingHeader = persist_header.clone();
$floatingHeader.children().width(function (i, val) {
return persist_header.children().eq(i).width();
});
$floatingHeader.css(“width”, persist_header.width()).addClass(“floatingHeader”);
persist_header.before($floatingHeader);
Thanks Teapot.
Worked like a charm with minor fixes.
Sorry, but I couldn’t get your code to work properly. It would generate the number of persist-header’s on the page for each persist-area. I put it inside an .each() and fixed that issue, but then it would only have the correct width for the first table on my page (all tables had dynamic widths). I rewrote it to the following.
Great! I was impressed how a few lines of JavaScript can do something so useful & needed.
More than this article, I really like your new theme! Every single pixel are so sexy!
Hey Mr. Coyier,
Would you mind me asking what you use for showing code in your blog posts? I looked for a blog article on this (and other) sites, and I found a few neat things, but nothing looked quite as nice as what you use. Thanks!
That’s great man. Very useful.
Gmail inbox has this. thanks chris for such great tips.
I did some android testing. It doesn’t do anything on the stock webkit browser. Firefox reports the event after I release my finger. Opera Mobile seems to report the event, but the header gets drawn in the wrong place. It has the same delay as FF.
I didn’t bother testing on Opera Mini.
There is a error in the script.
persistent-area and persist-area are mixed in html and javascript
damn you auto-correct!
Fantastic, Chris. Thanks a ton. Just what I needed for a current project.
For my needs, I wanted the element to appear only after scrolling down a bit. So to the element itself I added display: none, and then to .floatingHeader I added display: block.
Like this:
.elementClass {
display: none; /*hides element until needed*/
}
.floatingHeader {
position: fixed;
top: 0;
visibility: hidden;
display: block; /*shows element when needed*/
}
Example:
http://www.beamingbioneersvermont.com/presenters.php
[scroll down to see it in action]
Thanks again!
Thanks for this great piece of code. I’m implementing it on a productpage with a large table.
I wonder how I should alter the code so that the header disappears when the bottom of the header touches the end of the table. Now it disappears when the top of the header touches the end of the table. This results in a header appearing below the table.
or even better… let the header (thead) disappear when the bottom of the header touches the top of the footer (tfoot).
This code is too awesome!
In fact I’ve adapted it to work with the jQuery tablesorter plugin by making it into a widget. Check out a demo here.
And , in case anyone is interested, I have forked tablesorter in my github and made a bunch of improvements.
And yes, the widget can be applied to the original version of tablesorter :)
Thanks Chris!
Is there a way to make it work with jquery-2.0.2.min.js
Hi! Thanks for your tutorial. It´s fantastic!
Now I´m triying to implement this code in WordPress but I can´t do it.
I don´t know why but it doesn´t work. Can anyone help me please?
I think the problem is in line:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"
Cause without this linea it doesn´t work and my WordPress can´t read this line.
Thanks in advance
This seems like a bad idea duplicating the heading, what if you have attached event listeners to a specific ID? If you duplicate the code this breaks the functionality since it generates two blocks with the same IDs, breaking whatever is in the heading or duplicating event listeners. I just tried to implement and came across this issue, started switching over to classes but still it’s very hacky solution for what you’re trying to achieve in my opinion.
Hey man, thanks for the code “:).
But, what if I want to make a column fixed too? For example, if I want the header and the first column of a table to be fixed, but only when looking at them… Then, when scrolling down, they would disappear, just like you did…
How could I do that?
Thanks!
Thank you for that nice an simple sollution! works perfectly for my usecase!
Hey Chris I was trying to implement a similar thing. But I wanted that the position of the persistant-header should be below the main header of the web-page. I tried by changing the floating header as
.floatingHeader{
position: fixed;
top: 60px;
visibility: hidden;
}
where 60px is the height of the main header.
The persistent header timings dont seem to work perfectly. Are there any other changes that I need to make?