110

The bootstrap documentation on that topic is a little confusing to me. I want to achieve similar behaviour like in the docs with the affix navbar: The navbar is below a paragraph / page heading, and upon scrolling down it should first scroll along until reaching the top of the page, and then stick there fixed for further scrolldowns.

As jsFiddle does not work with the navbar concept, I've set up a separate page for usage as a minimal example: http://i08fs1.ira.uka.de/~s_drr/navbar.html

I use this as my navbar:

<div class="navbar affix-top" data-spy="affix" data-offset-top="50">
    <div class="navbar-inner">
        <div class="container">
            <div class="span12">
                <a class="brand" href="#">My Brand</a> 
                This is my navbar.
             </div>
        </div> <!-- container -->
    </div> <!-- navbar-inner -->
</div> <!-- navbar -->

I thinkg i would want data-offset-top to be of value 0 (since the bar should "stick" to the very top" but with 50 there is at least some effect watchable.

If also put the javascript code in place:

     <script>
        $(document).ready (function (){
            $(".navbar").affix ();
        });
     </script>

Any help appreciated.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Dynalon
  • 6,577
  • 10
  • 54
  • 84
  • are you trying to use affix() on the main nav-bar of your page? – Nithin Emmanuel Aug 22 '12 at 10:24
  • @NithinEmmanuel yes, see the javascript in the post or at the sample: http://i08fs1.ira.uka.de/~s_drr/navbar.html – Dynalon Aug 22 '12 at 10:44
  • why don't you use use `.navbar-fixed-top` instead of using affix()? – Nithin Emmanuel Aug 22 '12 at 10:53
  • 6
    @NithinEmmanuel because thats not what i want. `.navbar-fixed-top`would place the navbar to the top **all the time**. I want a page header ABOVE the navbar, and when scrolling down (and thus the navbar would bet scrolled away) it should stick to the top - then and only then. Bootstrap docs used the very same mechanisms as a subnav in their previous docs, sadly they have removed it for the 2.1.0 docs. – Dynalon Aug 22 '12 at 10:57
  • 1
    For one the nesting of your Javascript is incorrect. `)};` should be `});` – Owen Jun 06 '13 at 17:29

9 Answers9

160

I was having a similar problem, and I believe I found an improved solution.

Don't bother specifying data-offset-top in your HTML. Instead, specify it when you call .affix():

$('#nav').affix({
    offset: { top: $('#nav').offset().top }
});​

The advantage here is that you can change the layout of your site without needing to update the data-offset-top attribute. Since this uses the actual computed position of the element, it also prevents inconsistencies with browsers that render the element at a slightly different position.


You will still need to clamp the element to the top with CSS. Furthermore, I had to set width: 100% on the nav element since .nav elements with position: fixed misbehave for some reason:

#nav.affix {
    position: fixed;
    top: 0px;
    width: 100%;
}

One last thing: When an affixed element becomes fixed, its element no longer takes up space on the page, resulting in the elements below it to "jump". To prevent this ugliness, I wrap the navbar in a div whose height I set to be equal to the navbar at runtime:

<div id="nav-wrapper">
    <div id="nav" class="navbar">
        <!-- ... -->
    </div>
</div>

.

$('#nav-wrapper').height($("#nav").height());

Here's the obligatory jsFiddle to see it in action.

namuol
  • 9,816
  • 6
  • 41
  • 54
  • 6
    Great tip to remove the "jump" - completely overlooked the fact that this item gets removed and thought my issue was contained in the affix/js portion of my code. – Thomas Dec 27 '12 at 16:35
  • 1
    This answer is the best! Everything works as it should. The 'jump' part was the one missing in most tutorials I found. Thanks! – rikas Apr 08 '13 at 22:41
  • 1
    One important correction: Use offset() instead of position() in your jquery code in your first code snippet. Explanation: The position() function gives you the offset within the parent element, but offset() is its position within the document, which is what you actually want (yeah, the names are counter-intuitive). So your code wouldn't work if your affix element is inside another element. You should also only pass the "top" value like this: $("#nav").affix({ offset: { top: $('#nav').offset().top. And it should be inside a ready() block so you know the page layout is complete. – orrd Sep 10 '13 at 16:20
  • Nice! Works perfectly and it's very smooth. By the way, I'm looking for the same example with a sticky footer. If you know how to do that, can you update the jsFiddle? Thanks! – Jocelyn Nov 22 '13 at 16:23
  • @Jocelyn -- Shouldn't be much different -- you'll want to change all instances of "top" to "bottom" in the JS, and in the css do `bottom: 100%;` to stick it to the bottom of the page. – namuol Nov 23 '13 at 00:28
  • @Jocelyn -- Erp, that wont work. It'd have to be `bottom: 0;` and you'll need to set `margin-bottom: 0;` as well. But I'm not really sure how you want it to behave; a sticky footer is kind of a strange concept. – namuol Nov 23 '13 at 00:37
  • @Jocelyn. You can download the sticky-footer-navbar.css from bootstrap and then simply add a #footer to the bottom of the page, after the last container close. – jeynon May 14 '14 at 22:43
  • @namuol.. Great work on the script. I have it implemented but am having an issue getting a working collapsible nav bar in the nav space. When I attempt to insert some nav code from other examples (http://getbootstrap.com/examples/navbar-fixed-top/) it seems to not understand what I am trying to do. What needs to be added in your fiddle to make the nav pieces respond to a resize? – jeynon May 14 '14 at 22:49
  • If `#nav` has padding, it's not enough to use `$("#nav").height()` for the wrapper's height. I had to replace it with `$("#nav").outerHeight()` – awendt Jul 15 '15 at 21:35
  • How can I do it in bootstrap 4? It doesn't supports affix. – NightOwl Jun 19 '20 at 07:59
  • This answer is quite old. You may want to try using `position: sticky` instead! https://developer.mozilla.org/en-US/docs/Web/CSS/position – namuol Jun 21 '20 at 18:12
76

Just implemented this for the first time, and here's what I've found.

The data-offset-top value is the amount of pixels that you must scroll in order for the affixing effect to take place. In your case, once 50px is scrolled, the class on your item is changed from .affix-top to .affix. You'd probably want to set data-offset-top to about 130px in your use case.

Once this class change occurs, you must position your element in css by styling the positioning for class .affix. Bootstrap 2.1 already defines .affix as position: fixed; so all you need to do is add your own position values.

Example:

.affix {
    position: fixed; 
    top: 20px; 
    left: 0px;
}
Dave Kiss
  • 10,289
  • 11
  • 53
  • 75
  • 1
    Thx, that somewhat works. I've updated my example page to that. Problem is now, as soon as the navbar gets "affix" as css class, the navbar css class is discarded. but i think that is just a bug/shortcomming from bootstrap. Won't be easy to change without doiing it in LESS and rebuilding bootstrap. – Dynalon Aug 23 '12 at 06:23
  • It looks to me as the `.navbar` class is preserved - are you sure? – Dave Kiss Aug 23 '12 at 16:26
  • Thanks for this answer, I spent ages trying to figure this out – Reuben Sep 07 '12 at 23:40
  • See [my answer](http://stackoverflow.com/a/13151016/742156) for a slightly-improved, more general solution. – namuol Jan 21 '13 at 09:59
5

To fix this very issue I have modified the affix plugin to emit a jQuery event when an object is affixed or unaffixed.

Here is the pull request: https://github.com/twitter/bootstrap/pull/4712

And the code: https://github.com/corbinu/bootstrap/blob/master/js/bootstrap-affix.js

And then do this to attach the navbar:

<script type="text/javascript">
$(function(){
    $('#navbar').on('affixed', function () {
        $('#navbar').addClass('navbar-fixed-top')
    });

    $('#navbar').on('unaffixed', function () {
        $('#navbar').removeClass('navbar-fixed-top')
    });
});
</script>
Corbin U
  • 51
  • 1
3

You need to remove .affix() from your script.

Bootstrap gives the option of accomplishing things either via data-attributes or straight JavaScript most of the time.

jonsca
  • 10,218
  • 26
  • 54
  • 62
2

I've got this from the twitterbootstrap's source code and it's working pretty well:

HTML:

<div class="row">
    <div class="span3 bs-docs-sidebar">
        <ul id="navbar" class="nav nav-list bs-docs-sidenav">
            ...
        </ul>
    </div>
</div>

CSS:

.bs-docs-sidenav {
    max-height: 340px;
    overflow-y: scroll;
}

.affix {
    position: fixed;
    top: 50px;
    width: 240px;
}

JS:

$(document).ready(function(){
    var $window = $(window);
    setTimeout(function () {
        $('.bs-docs-sidenav').affix({
            offset: {
                top: function (){
                    return $window.width() <= 980 ? 290 : 210
                }
            }
        })
    }, 100);
});
1

You just need to remove the script. Here is my example:

<!DOCTYPE html>
<html>

<head>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.8.0.min.js"></script>
<script type="text/javascript" src="http://netdna.bootstrapcdn.com/twitter-bootstrap/2.1.0/js/bootstrap.min.js"></script>

  <style>
  #content {
    width: 800px;
    height: 2000px;
    background: #f5f5f5;
    margin: 0 auto;
  }
  .menu {
    background: #ccc;
    width: 200px;
    height: 400px;
    float: left;
  }
  .affix {
    position: fixed;
    top: 20px;
    left: auto;
    right: auto;
  }
  </style>

</head>
<body>
    <div id="content">
        <div style="height: 200px"></div>

        <div class="affix-top" data-spy="affix" data-offset-top="180">
            <div class="menu">AFFIX BAR</div>
        </div>
    </div>
</body>
</html>
ЯegDwight
  • 24,821
  • 10
  • 45
  • 52
Beckovsky
  • 21
  • 2
1

Thanks to namuol and Dave Kiss for the solution. In my case I had a tiny problem with navbar height and width when I used afflix and collapse plugins together. The problem with width can be easily solved inheriting it from parent element (container in my case). Also I could manage to make it collapsing smoothly with a bit of javascript (coffeescript actually). The trick is to set wrapper height to auto before collapse toggle occurs and fix it back after.

Markup (haml):

#wrapper
  #navbar.navbar
    .navbar-inner
      %a.btn.btn-navbar.btn-collapse
        %span.icon-bar
        %span.icon-bar
        %span.icon-bar

      #menu.nav-collapse
        -# Menu goes here

CSS:

#wrapper {
  width: inherit;
}

#navbar {
  &.affix {
    top: 0;
    width: inherit;
  }
}

Coffeescript:

class Navigation
  @initialize: ->
    @navbar = $('#navbar')
    @menu = $('#menu')
    @wrapper = $('#wrapper')

    @navbar.affix({offset: @navbar.position()})
    @adjustWrapperHeight(@navbar.height())

    @navbar.find('a.btn-collapse').on 'click', () => @collapse()

    @menu.on 'shown', () => @adjustWrapperHeight(@navbar.height())
    @menu.on 'hidden', () => @adjustWrapperHeight(@navbar.height())

  @collapse: ->
    @adjustWrapperHeight("auto")
    @menu.collapse('toggle')

  @adjustWrapperHeight: (height) ->
    @wrapper.css("height", height)

$ ->
  Navigation.initialize()
Voldy
  • 12,829
  • 8
  • 51
  • 67
0

My solution for attach the navbar :

function affixnolag(){

    $navbar = $('#navbar');
    if($navbar.length < 1)
        return false;

    h_obj = $navbar.height();

    $navbar
        .on('affixed', function(){      
            $navbar.after('<div id="nvfix_tmp" style="height:'+h_obj+'px">');
        })
        .on('unaffixed', function(){
            if($('#nvfix_tmp').length > 0)
                $('#nvfix_tmp').remove();
        });
 }
0

Similar to the accepted answer, you can also do something like the following to do everything in one go:

$('#nav').affix({
  offset: { top: $('#nav').offset().top }
}).wrap(function() {
  return $('<div></div>', {
    height: $(this).outerHeight()
  });
});​

This not only invokes the affix plugin, but will also wrap the affixed element in a div which will maintian the original height of the navbar.

Adrian
  • 41
  • 3