preloaded image preloaded image preloaded image

A "Sticky" Scrolling Element

If it ain't fixed, don't break it...or something?

Ok, so the basic idea here is that you might want an element in the page to have a mostly fixed position. Which is to say, you might want it to behave as a fixed object throughout most of the page scrolling, but you might also want it to dock itself at some point in the scroll process. So how might we do that?

Well there are a couple of options with this but only one that seems right to me. Some people go the hard route and use absolute positioning for the entire effect and then simply change the top/bottom and left/right settings (as the case may be) to give the appearance of fixed positioning. Granted, this approach works (I used to use it). But it is annoyingly choppy and from my tests it seems to slow my browser down considerably as well. Not the option for me, unless something in the page makes a true fixed positioning impractical.

So my preferred method is to simply have a default state of absolute positioning. I do this as a fail-safe when javascript is not available or activated by the user and also because most times (for me at least) when a user will enter a page they will be doing so at the top of the document which is usually the point in the scrolling cycle wherein the "sticky" scrolling element is already docked. That means there won't be any unsightly jitter when the script initializes. Then, I simply calculate how far the user has to scroll before they reach the point at which they will "undock" the element. When that point is reached, I switch the element to fixed positioning and apply my "buffer" or padding distance from the top of the viewport (via the "top" position setting). Any time the user resizes the window or scrolls the page I check my scroll position again and if we have crossed the threshold back into "docked" space I revert the element back to absolute positioning and remove the "buffer" or padding (via the "top" position setting). This works nicely and best of all, you aren't manipulating the DOM by changing the "top" positiong setting each and every single time a page movement takes place.

So here's the script. Notice that I have packaged the script into a variable which saves on global namespace pollution a bit:

var sticky_page_scroll = function(){ var sticky_element_id="sidebar"; //enter your sticky element's ID here... var min_y_distance=30; //enter the number of px from the viewport edge you wish to pad your sticky element by... var initial_y_pos; var initialize = function(){ if(!document.getElementById(sticky_element_id)){ setTimeout(function(){sticky_page_scroll.initialize();},5); } else{ //save the initial vertical position of the element so we can find our threshold again later on... initial_y_pos=find_pos(document.getElementById(sticky_element_id))[1]; //attach event handlers to watch changes in page scroll or dimensions... if(window.addEventListener){ //for modern versions of IE and for FF, etc. window.addEventListener("scroll",scroll); window.addEventListener("resize",scroll); } else{ //for older versions of IE that support this method rather than addEventListener... window.attachEvent("onscroll",scroll); window.attachEvent("onresize",scroll); } //go ahead and do the scroll check once right now before anything else happens (in case we've refreshed the page and are already scrolled down the page, for example) scroll(); } //congratualtions, you are now initialized! } var find_pos = function(element){ var curleft = curtop = 0; if(element.offsetParent){ do{ curleft += element.offsetLeft; curtop += element.offsetTop; }while(element = element.offsetParent); } return [curleft,curtop]; } var posTop = function(){ return typeof window.pageYOffset != 'undefined' ? window.pageYOffset : document.documentElement && document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop ? document.body.scrollTop : 0; }; var scroll = function(){ var y = posTop(); //number of px we have so far scrolled down the page if(y>initial_y_pos-min_y_distance-1){ //if we have scrolled more than our initial position, less the padding, less 1, then we switch up to fixed positioning and set our "top" value to the amount of padding we wanted... document.getElementById(sticky_element_id).style.position="fixed"; document.getElementById(sticky_element_id).style.top=min_y_distance + "px"; } else{ //otherwise, we assign absolute positioning and clear out the "top" value (setting it to zero, for my needs)... document.getElementById(sticky_element_id).style.position="absolute"; document.getElementById(sticky_element_id).style.top="0px"; } }; return{ //make the initialize and scroll functions public so that we can use them... initialize:initialize, scroll:scroll }; }();

Simply insert your sticky scrolling element's ID, set the amount of top padding you want the sticky scroller to have, and then run the initialize function (using the syntax "sticky_page_scroll.initialize();") once the page has loaded and you're off and running!