HTML Sticky Headers

January 2012 ยท 2 minute read

Sticky headers are heading elements that stick to the top of the window as the user scrolls down the page. This is useful since you can use them to retain the contextual information of the section regardless of the position.

In my case I needed to have multiple headers in the hourly forecast list for hourweather.com.

I found there wasn’t much out there about this topic, especially when you want to have multiple sticky headers per page so I thought I’d share my implementation(which is pretty rough) for others to reference for the main ideas.

Basic Concept

Sticky headers basically work by toggling the headers position type when it reaches the top of the window to fixed so that it sticks in place. The rest of the logic really just handles the flipping from one stuck headers to the next as the scrolling continues up or down the page.

Demo

Check out hourweather.com as its used in the hourly forecast listings.

Code

This code uses JQuery since I was already using it on the page. You could remove the references pretty easily since I only used some selectors and CSS manipulations.

function setupStickyHeaders() {
    //get initial offset positions of the headers
    var headers = [];
    $.each($('div.day-start'), function(i, headerDiv) {
        var header = $(headerDiv);
        headers.push({element\:header, orgOffset\: header.offset().top});
    });

    //if no headers on this page then stop
    if(headers.length == 0)
        return;

    //get the original css for the
    var orgCSS = {width\:headers[0].element.css('width'), position\:headers[0].element.css('position'), height\:headers[0].element.css('height')};

    //handle scrolling
    $(window).scroll(function(){
        var scrollTop = $(this).scrollTop();


        //unstick headers
        for(i in headers) {
            var header = headers[i];
            if(!isStuck(header)) break;

            if(scrollTop < header.orgOffset) {
                header.element.css('position', orgCSS.position).css('top','').prev().remove();
                if(i > 0)
                    headers[i-1].element.css('z-index', 1);
            }
        }

        //stick headers
        for(i in headers) {
            var header = headers[i];
            //make sure any stuck headers are stuck in the right place(fast scrolling sometimes messes this up)
            if(isStuck(header))
                header.element.css('top','0')

            //skip this header if its bellow the top of the window
            if(scrollTop < header.orgOffset - parseInt(orgCSS.height))
                break;

            //if the header is already stuck then ignore it
            if(!isStuck(header)) {
                if(scrollTop > header.orgOffset) {
                    //stick the header
                    header.element.css('width', orgCSS.width).css('position', 'fixed').css('top','0').before('<div class=day-start> </div>');
                    if(i > 0)
                        headers[i-1].element.css('z-index', -1);
                }else if(i > 0)
                    //hide the element since it should be off the screen now
                    headers[i-1].element.css('top', header.orgOffset - scrollTop - parseInt(orgCSS.height));
            }
        }
    });
}

function isStuck(header) {return header.element.css('position') === 'fixed'}