How to fix a header on scroll
Asked Answered
M

16

63

I am creating a header that once scrolled to a certain amount of pixels it fixes and stays in place.

Can I do this using just css and html or do i need jquery too?

I have created a demo so you can understand. Any help would be great!

http://jsfiddle.net/gxRC9/4/

body{
     margin:0px;
     padding:0px;
}
 .clear{
     clear:both;
}
 .container{
     height:2000px;
}
 .cover-photo-container{
     width:700px;
     height: 348px;
     margin-bottom: 20px;
     background-color:red;
}
 .small-box{
     width:163px;
     height:100px;
     border:1px solid blue;
     float:left;
}
 .sticky-header{
     width:700px;
     height:50px;
     background:orange;
     postion:fixed;
}


Marniemaro answered 3/10, 2013 at 11:54 Comment(4)
you do not do this without the help of JSBichromate
The problem with this approach and the subsequent answers is that you end up relying on the container being no more than the 2000px that you specify. This would fail in production because either you would need to keep adjusting. Or you would be asking the copywriters to never add more content beyond a certain word count. Either way this approach does not scale.Edgy
Something with jquery and CSS can help you to achieve sticky header on scroll - kvcodes.com/2017/03/jquery-simple-sticky-header-on-scrollSuite
check my medium article here about sticky headers which should be helpful :DBuffum
M
143

You need some JS to do scroll events. The best way to do this is to set a new CSS class for the fixed position that will get assigned to the relevant div when scrolling goes past a certain point.

HTML

<div class="sticky"></div>

CSS

.fixed {
    position: fixed;
    top:0; left:0;
    width: 100%; }

jQuery

$(window).scroll(function(){
  var sticky = $('.sticky'),
      scroll = $(window).scrollTop();

  if (scroll >= 100) sticky.addClass('fixed');
  else sticky.removeClass('fixed');
});

Example fiddle: http://jsfiddle.net/gxRC9/501/


EDIT: Extended example

If the trigger point is unknown but should be whenever the sticky element reaches the top of the screen, offset().top can be used.

var stickyOffset = $('.sticky').offset().top;

$(window).scroll(function(){
  var sticky = $('.sticky'),
      scroll = $(window).scrollTop();

  if (scroll >= stickyOffset) sticky.addClass('fixed');
  else sticky.removeClass('fixed');
});

Extended example fiddle: http://jsfiddle.net/gxRC9/502/

Memorialist answered 3/10, 2013 at 12:0 Comment(13)
thanks for your help. can you make the yellow box fixed not the red one?Marniemaro
Of course, now you've got that code you can substitute any classes in there. I've updated my answer.Memorialist
You did good trick i don't know why some one downvoted.Any way +1 from me.. cheers:)Pamalapamela
@Coop yes even my answer was perfect too, but still two negative votes.. huh if someone gives negative vote, he/she must explain the reason huhArrange
@PaulDesigner Just a little tweak, change the 100 in "if ($(window).scrollTop() >= 100) {" to 180. Looks abit cleaner ;)Arielle
@PaulDesigner Was a bit slow in getting my answer done but just to extend this answer, if you only want the header to move once you have scrolled past it here is a fiddlePax
Can you specify how many pixels you want the user to scroll before the header snaps into place??? –Marniemaro
I believe that you may have forgotten a closing </div> before the sticky header. Please check your code.Edgy
This is a great answer, however, once you apply the fixed class the page becomes that much shorter and content will jump up, and if page is of a length where the scroll distance is less than the height of the header element, it will appear to jump and not let you see the bottom of the page. I have modified this answer below.Tewell
@CraigJacobs Your issue can be easily solved with a couple of lines of CSS. By ensuring the sticky element is always out of the flow of the site (position: absolute) and relevant padding in the body to make space for it.Memorialist
and what if we dont know the padding i.e in this case 100px ?Whoopee
you can use "sticky.toggleClass('fixed', scroll >= stickyOffset);" instead of the if-else statementHaw
great. May you want to consider initialising the 'sticky' variable once before (and outside) of the scroll function.Celtic
V
15

I have modified the Coop's answer. Please check the example FIDDLE Here's my edits:

$(window).scroll(function(){
  if ($(window).scrollTop() >= 330) {
    $('.sticky-header').addClass('fixed');
   }
   else {
    $('.sticky-header').removeClass('fixed');
   }
});
Ventose answered 8/3, 2014 at 11:12 Comment(2)
Best answer so far because this actually worked cleaner when you scroll past the element.Edgy
Can this be implemented like fix header to top after scrolling landing page div.Lankester
M
13

Coop's answer is excellent.
However it depends on jQuery, here is a version that has no dependencies:

HTML

<div id="sticky" class="sticky"></div>

CSS

.sticky {
  width: 100%
}

.fixed {
  position: fixed;
  top:0;
}

JS
(This uses eyelidlessness's answer for finding offsets in Vanilla JS.)

function findOffset(element) {
  var top = 0, left = 0;

  do {
    top += element.offsetTop  || 0;
    left += element.offsetLeft || 0;
    element = element.offsetParent;
  } while(element);

  return {
    top: top,
    left: left
  };
}

window.onload = function () {
  var stickyHeader = document.getElementById('sticky');
  var headerOffset = findOffset(stickyHeader);

  window.onscroll = function() {
    // body.scrollTop is deprecated and no longer available on Firefox
    var bodyScrollTop = document.documentElement.scrollTop || document.body.scrollTop;

    if (bodyScrollTop > headerOffset.top) {
      stickyHeader.classList.add('fixed');
    } else {
      stickyHeader.classList.remove('fixed');
    }
  };
};

Example

https://jsbin.com/walabebita/edit?html,css,js,output

Matelda answered 31/3, 2016 at 16:54 Comment(0)
G
11

Just building on Rich's answer, which uses offset.

I modified this as follows:

  • There was no need for the var sticky in Rich's example, it wasn't doing anything

  • I've moved the offset check into a separate function, and called it on document ready as well as on scroll so if the page refreshes with the scroll half-way down the page, it resizes straight-away without having to wait for a scroll trigger

     jQuery(document).ready(function($){
         var offset = $( "#header" ).offset();
         checkOffset();
    
         $(window).scroll(function() {
             checkOffset();
         });
    
         function checkOffset() {
             if ( $(document).scrollTop() > offset.top){
                 $('#header').addClass('fixed');
             } else {
                 $('#header').removeClass('fixed');
             } 
         }
    
     });
    
Govern answered 1/3, 2016 at 12:20 Comment(0)
S
10

I know Coop has already answered this question, but here is a version which also tracks where in the document the div is, rather than relying on a static value:

http://jsfiddle.net/gxRC9/16/

Javascript

var offset = $( ".sticky-header" ).offset();
var sticky = document.getElementById("sticky-header")

$(window).scroll(function() {

    if ( $('body').scrollTop() > offset.top){
        $('.sticky-header').addClass('fixed');
    } else {
         $('.sticky-header').removeClass('fixed');
    } 

});

CSS

.fixed{
     position: fixed;
    top: 0px;
}
Sutler answered 3/10, 2013 at 12:14 Comment(7)
Yeh offset.top is good. This is actually the more full method I wuld use for responsive designs where div heights are likely to change.Memorialist
Yeap. Not just responsive, it could be anything, like an image changing height, or more or less text in a div above the sticky element.Sutler
i was thinking about how Paul Designer going to set his static scroll amount manually every time according to document size. But you did good job @SutlerPamalapamela
Can you specify how many pixels you want the user to scroll before the header snaps into place???Marniemaro
Do you mean, how many pixels past the sticky element? Try this: jsfiddle.net/gxRC9/18Sutler
Cool, well try here: jsfiddle.net/gxRC9/18 - just change the value of additionalPixelsSutler
I have just tried it and when you change the pixel value it doesnt seem to work??Marniemaro
D
9

Glorious, Pure-HTML/CSS Solution

In 2019 with CSS3 you can do this without Javascript at all. I frequently make sticky headers like this:

body {
  overflow-y: auto;
  margin: 0;
}

header {
  position: sticky; /* Allocates space for the element, but moves it with you when you scroll */
  top: 0; /* specifies the start position for the sticky behavior - 0 is pretty common */
  width: 100%;
  padding: 5px 0 5px 15px;
  color: white;
  background-color: #337AB7;
  margin: 0;
}

h1 {
  margin: 0;
}

div.big {
  width: 100%;
  min-height: 150vh;
  background-color: #1ABB9C;
  padding: 10px;
}
<body>
<header><h1>Testquest</h1></header>
<div class="big">Just something big enough to scroll on</div>
</body>
Drumbeat answered 19/3, 2019 at 15:53 Comment(2)
position: -webkit-sticky; /* Safari */Brewery
This might come handy when deciding whether you can use this solution or not: caniuse.com/#feat=css-stickyRevet
K
8

Can be done by using only CSS

.sticky-header {
  position: sticky;
  top: 0;
  background: orange;
}

body {
  margin: 0px;
  padding: 0px;
}

.clear {
  clear: both;
}

.container {
  height: 2000px;
}

.cover-photo-container {
  width: 700px;
  height: 100px;
  margin-bottom: 20px;
  background-color: red;
}

.small-box {
  width: 163px;
  height: 100px;
  border: 1px solid blue;
  float: left;
}

.sticky-header {
  position: sticky;
  top: 0;
  background: orange;
}
<div class="container">
  <div class="cover-photo-container">
    Please scroll
  </div>
  <div class="small-box"></div>
  <div class="small-box"></div>
  <div class="small-box"></div>
  <div class="small-box"></div>
  <div class="clear"></div>
  <div class="sticky-header">
    This needs to be fixed when hits top of screen
  </div>
</div>
Knickerbockers answered 30/10, 2021 at 8:40 Comment(0)
T
6

Coops answer is a good, simple solution, however, once you apply the fixed class the page becomes that much shorter and content will "jump" up, and if page is of a length where the scroll distance is less than the height of the header element, it will appear to jump and not let you see the bottom of the page.

The answer I found was to add a spacer div above the element that will become fixed (nav element in my case), and set it to the same height as the nav element, and set it to display none.

When adding the .fixed class to the nav, show the .nav-spacer div, and when removing it, hide it. Since the height of the page changes instantly I have set the duration to 0.

HTML

<header>the element above the element that will become fixed</header>
<div class="nav-spacer"></div>
<nav></nav>

CSS

nav {
    position: relative;    
    height: 100px;
}
.nav-spacer{
    position: relative;
    height: 100px;
    display: none;
}
.fixed {
    position: fixed;
    top:0;
    left:0;
    width: 100%;
    /* I like to add a shadow on to the fixed element */
    -webkit-box-shadow: 0px 5px 10px 1px rgba(0,0,0,0.25);
    -moz-box-shadow: 0px 5px 10px 1px rgba(0,0,0,0.25);
    box-shadow: 0px 5px 10px 1px rgba(0,0,0,0.25);
}

JavaScript

var stickyOffset = $('nav').offset().top;

$(window).scroll(function(){
    if ($(window).scrollTop() >= stickyOffset){
        $('nav').addClass('fixed');
        //this makes the page length equal to what it was before fixing nav
        $('.nav-spacer').show(0); 
    }
    else {
        $('nav').removeClass('fixed');
        $('.nav-spacer').hide(0);
    }
});
Tewell answered 29/9, 2016 at 4:12 Comment(0)
S
3

Or just simply add a span tag with the height of the fixed header set as its height then insert it next to the sticky header:

$(function() {
  var $span_height = $('.fixed-header').height;
  var $span_tag = '<span style="display:block; height:' + $span_height + 'px"></span>';

  $('.fixed-header').after($span_tag);
});
Scenarist answered 16/11, 2015 at 10:56 Comment(0)
N
2
 $(document).ready(function(){

    var div=$('#header');
    var start=$(div).offset().top;

    $.event.add(window,'scroll',function(){
        var p=$(window).scrollTop();
        $(div).css('position',(p>start)?'fixed':'static');
        $(div).css('top',(p>start)?'0px':'');

    }); 
});

It works perfectly.

Naman answered 4/8, 2015 at 13:7 Comment(0)
S
2

The chosen solution did not fit well in my page. So this is a more advanced version that works with bootstrap.

The javascript

var stickyOffset = $('.sticky-header').offset().top;

$(window).scroll(function () {
    var sticky = $('.sticky-header'),
        scroll = $(window).scrollTop(),
        header = $('.fixed-header-background');
    sticky.each(function() {
        var left = $(this).offset().left;
        $(this).data('left', left);//I store the left offset
    });

    if (scroll >= stickyOffset) {
        sticky.addClass('fixed');
        header.css('display', 'block');
        sticky.each(function() {
            $(this).css('left', $(this).data('left'));//I set the left offset
        });
    } else {
        sticky.removeClass('fixed');
        header.css('display', 'none');
        sticky.each(function () {
            $(this).css('left', '');//I remove the left offset
        });
    }
});

The CSS

.fixed-header-background {
    display: none;
     position: fixed;
    top: 50px;
    width: 100%;
    height: 30px;
    background-color: #fff;
    z-index: 5;
    border-bottom-style: solid;
    border-bottom-color: #dedede;
    border-bottom-width: 2px;
}

.fixed{
     position: fixed;
    top: 52px;
    z-index: 6;
}

And the HTML

    <div class="fixed-header-background"></div>
<table class="table table-hover table-condensed">
        <thead>
            <tr>
                <th></th>
                <th><span class="sticky-header">My header 1</span></th>
                <th><span class="sticky-header">My header 2</span></th>
            </tr>
        </thead>
        <tbody>
[....]

        </tbody>
    </table>
Syzran answered 5/8, 2015 at 9:37 Comment(0)
M
1
custom scroll Header Fixed in shopify:

$(window).scroll(function(){
  var sticky = $('.site-header'),
      scroll = $(window).scrollTop();

  if (scroll >= 100) sticky.addClass('fixed');
  else sticky.removeClass('fixed');
})


css:


header.site-header.border-bottom.logo--left.fixed {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    z-index: 9;
}
Miskolc answered 15/1, 2021 at 3:39 Comment(1)
You should make an effort to explain your answer here.Ahmadahmar
E
0

Hopefully this one piece of an alternate solution will be as valuable to someone else as it was for me.

Situation:
In an HTML5 page I had a menu that was a nav element inside a header (not THE header but a header in another element).
I wanted the navigation to stick to the top once a user scrolled to it, but previous to this the header was absolute positioned (so I could have it overlay something else slightly).
The solutions above never triggered a change because .offsetTop was not going to change as this was an absolute positioned element. Additionally the .scrollTop property was simply the top of the top most element... that is to say 0 and always would be 0.
Any tests I performed utilizing these two (and same with getBoundingClientRect results) would not tell me if the top of the navigation bar ever scrolled to the top of the viewable page (again, as reported in console, they simply stayed the same numbers while scrolling occurred).

Solution
The solution for me was utilizing

window.visualViewport.pageTop

The value of the pageTop property reflects the viewable section of the screen, therefore allowing me to track where an element is in reference to the boundaries of the viewable area.
This allowed a simple function assigned to the scroll event of the window to detect when the top of the navigation bar intersected with the top of the viewable area and apply the styling to make it stick to the top.

Probably unnecessary to say, anytime I am dealing with scrolling I expect to use this solution to programatically respond to movement of elements being scrolled.
Hope it helps someone else.

Elberfeld answered 24/10, 2017 at 19:41 Comment(0)
F
0

The simplest way is: HTML:

<header>
        <h1>Website</h1>
</header>

CSS:

header{
    position: sticky;
    top: 0;
}
Fahy answered 2/9, 2020 at 14:55 Comment(0)
B
0

If you are using React I recently published a custom hook which enables you to do this. You need to supply a ref to the sticky element as well as a ref to the element you want it to stick to the top of. It handles the screen "jump" (as mentioned in other responses) for you as well.

const sticky = useRef<HTMLDivElement>(null)
const container = useRef<HTMLDivElement>(null)

useStickyScroll({
    element: sticky,
    container: container
})
Buffalo answered 11/1, 2021 at 18:42 Comment(0)
N
0

My solution for show header if user scrolling document from bottom to top.

JS:

const isMobile = window.innerWidth < 768
const $header = $('#header')
let lastScrollTop = 0
if ( isMobile ) {
    $(window).scroll(function () {
        const scrollTop = window.pageYOffset || document.documentElement.scrollTop

        if ( scrollTop < lastScrollTop ) {
            $header.removeClass('header_top')
        } else {
            $header.addClass( 'header_top' )    
        }

        lastScrollTop = scrollTop <= 0 ? 0 : scrollTop
    })
}

CSS

.header {
    position: fixed;
    top: 0;
    z-index: 70;
    width: 100vw;
    transition: top .5s;
    height: 62px;
}
.header_top {
    top: -62px;
}
Nw answered 11/8, 2021 at 9:16 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.