19

I've read the documentation, and I feel like I'm doing exactly what they show in their examples. Yet when I try it, I can't get this to work. I'd like it to work in a way similar to the docs. It becomes position:fixed after scrolling past the header, and then once it touches the footer it becomes position:absolute and sticks to the bottom.


DEMO: http://jsfiddle.net/uvnGP/1/

JS

$("#account-overview-container").affix({
    offset: {
        top: $("header").outerHeight(true),
        bottom: $("footer").outerHeight(true)
    }
});

SASS

#account-overview-container {
    &.affix{
        top:10px;
        bottom:auto;
    }

    &.affix-top{
        //Do I need this?
    }

    &.affix-bottom{
        position:absolute;
        top:auto;
        bottom:140px;
    }
}
Chris Barr
  • 29,851
  • 23
  • 95
  • 135
  • 2
    I'm having the same problem. Once it reaches the bottom constraint I see a flicker and notice that it rapidly goes back and forth between 'affix' and 'affix-bottom'. Did you ever figure it out? – Ross Hambrick Jul 09 '13 at 22:15
  • 3
    Not sure if there's a solution. This issue seems to have been completely ignored. – Seed Sep 29 '13 at 16:30
  • If you are using bootstrap 3, there is a bug that was fixed in master, but not released yet: https://github.com/twbs/bootstrap/commit/0fab6e7ea77ef838adaf78adf56c951ae6e16cdc#diff-3bd3c6fb42dd795d80a9306802e8c4da – michelpm Jan 19 '14 at 01:03

10 Answers10

15

There some changes from your code but the solution fits for variable header and footer height, responsive width and can be change for fixed header.

Here is the link

http://jsfiddle.net/uvnGP/131/

The problem was, when the affix hit the bottom, there was a top and a bottom css style added to your container (#sidebar), the way to fix it is to reset that bottom style to auto, that way only the top style will acto on your container

here is the code:

var headerHeight = $('header').outerHeight(true); // true value, adds margins to the total height
var footerHeight = $('footer').innerHeight();
$('#account-overview-container').affix({
    offset: {
        top: headerHeight,
        bottom: footerHeight
    }
}).on('affix.bs.affix', function () { // before affix
    $(this).css({
        /*'top': headerHeight,*/    // for fixed height
            'width': $(this).outerWidth()  // variable widths
    });
}).on('affix-bottom.bs.affix', function () { // before affix-bottom
    $(this).css('bottom', 'auto'); // THIS is what makes the jumping
});
Stedy
  • 7,359
  • 14
  • 57
  • 77
Paco Rivera
  • 151
  • 1
  • 3
  • This works well and you can use `auto !important` for the `affix-bottom` in the CSS instead of the jquery handler: http://www.bootply.com/PWvY5VyzSy Just another option. – Carol Skelly Mar 30 '16 at 15:18
  • Thanks man.. i know sticky exists nowdays, but needed this solution ;) – Sagive Oct 23 '21 at 16:46
3

If you don't mind using 'data-' attributes in the markup instead of SASS, this should work:

http://www.bootply.com/l95thIfavC

You'll also see that I removed the JS affix.

HTML

<div class="well well-small affix-top" data-spy="affix" data-offset-top="150" id="account-overview-container">

CSS

body{
    position:relative;
}
header,
footer{
    background: #ccc;
    padding:40px 0;
    text-align:center;
}
header{
    margin-bottom: 10px;
}

.affix-top {
    top: 155px;
    position:absolute;
}
.affix {
    bottom:140px;
}

Update for Bootstrap 3

To use affix-bottom you basically need to set the height of the footer or space below the affixed element. Here's an example: http://www.bootply.com/l95thIfavC

Carol Skelly
  • 351,302
  • 90
  • 710
  • 624
  • 16
    So how does one get it to work with both the top AND the bottom affix positions? This seems to only deal with one of them. Every time I try to do both I get crazy flashing as it toggles between the two positions when I'm near the bottom. – Chris Barr Mar 12 '13 at 01:08
  • @michelpm, nope, I sure didn't :( I gave up on the bottom affix since everyone else seemed to have the same problem and there was no working solution I saw to actually fix this. – Chris Barr Jun 02 '13 at 21:05
  • 1
    @ChrisBarr I managed to get it working, the values of bottom on css and the plugin weren't matching. The affix as it is written it either evaluate to pin or evaluate to unpin, since both are true, on every scroll it toggles the pin, causing the crazy flashing you mentioned. – michelpm Jun 04 '13 at 00:09
  • @ChrisBarr I detailed the steps to get it working on your app on my answer. If you like inspecting on the browser, you might give a look on the quora.com upvote/downvote buttons, they work flawlessly. – michelpm Jun 04 '13 at 00:12
  • 1
    Oh really? In that case, could you look at the fiddle demo I linked to and point out what's wrong with it? – Chris Barr Jun 04 '13 at 01:05
1

I managed to make it work in my app, it is a matter of making the bottom styling consistent with the bottom passed to the affix call. If your footer have fixed height and the nearest positioned parent is the body, then it is matter of passing the same value for both. That works well enough for the Bootstrap 2.3's docs as seen here and here.

--

Now assuming you do not have a fixed height footer:

Step 1: Nearest positioned parent (offset parent)

First of all, we use absolute positioning to stick it to the bottom, but since your nearest positioned parent will be the body (that includes your footer) you don't have a fixed value for the bottom. To fix that we have to make a new positioned parent that encloses everything but the footer (important: header will probably be outside too). Set position: relative; to either the top .container or one of its ancestors (important: footer must be after this element).

Step 2: Footer height

Now, your bottom is relative to its container instead of body, but the bottom value of affix (used to know when to stick the affix to the bottom) is still relative to the end of the body, so you have to pass it a function that calculate the height of the elements after the container, if it is only the footer then this suffice: $('footer').outerHeight(true).

Step 3: Troubleshooting

Now, you have it all correct, but three things may still cause incorrect stick position and crazy flashing:

  1. Other visual elements besides the footer that you didn't take into account, like second footers or that are not always showing, like the useful rails-footnotes gem;

  2. Browser extensions that append elements to the DOM, if they are hidden or not in the flow then you shouldn't count their height, the easier way to get around that is to count only the elements your app know, but then an extension could end up breaking your app. You could test if an extension is causing you problems by using a browser or configuration that you don't have extensions installed, or just by going to "porn mode" (Incognito on Chromium (Ctrl+Shift+n), Private Browsing on Firefox (Ctrl+Shift+p)) if you haven't enabled extensions on it.

  3. Javascript libraries also append elements to the DOM, you should not count their height as well.

To solve these you could try to make it work with the whole word (CoffeeScript with Underscore.js):

_($('footer').nextAll(':visible').addBack())
  .filter((el) -> el.offsetParent?)
  .reduce(((memo, el) -> memo + $(el).outerHeight(true)), 0)

Same thing, with Javascript and jQuery.fn.each:

var sum = 0
$('footer').nextAll(':visible').each(function() {
  this.offsetParent != null && sum += $(this).outerHeight(true)
}
return sum

Or you could account only for the elements you know (I prefer this, the other one I coded for the challenge):

$('footer').outerHeight(true) + ($('#footnotes_debug').outerHeight(true) || 0)
michelpm
  • 1,795
  • 2
  • 18
  • 31
1

Short answer: Beware that content inside your affix is not using box-sizing: border-box; if you are using borders.

Explanation: The jQuery offset() function used in affix.js to calculate the current position of the element relative to the document does not take border's into account.

Note: jQuery does not support getting the offset coordinates of hidden elements or accounting for borders, margins, or padding set on the body element.

So if the content inside your affix has a border and it's box-sizing is set to border-box, the height will include the border but the offset will not, causing the calculation affix uses to be slightly off.

web-tiki
  • 99,765
  • 32
  • 217
  • 249
daddeo
  • 121
  • 1
  • 2
1

Solution of Paco Rivera works! But not in the way he intended. Wat really removes the flickering (jumping) problem is this line:

.on('affix.bs.affix', function () { // before affix
    $(this).css({
        'width': $(this).outerWidth()  // variable widths
    });
})

Weired enough you don't even have to set the CSS width. Just reading this parameter helps already. I simplified the code to the following fragment and it still works:

.on('affix.bs.affix', function () { // before affix
    $(this).width();
});

I will keep it like that untill the next release of Bootstrap Affix plugin, where I hope they will fix it once and for all.

Konstantin
  • 11
  • 2
0

Your fiddle seems to work as desired if you increase the window size a bit on the fiddle. Considering that the website would be unusable at the dimensions where the affix breaks, you should either ignore it, or be changing the layout to work with the smaller space instead of trying to fix the problem. The bootstrap affix in the documentation isn't used for smaller resolutions, and I'd follow suit.

Alexander Mistakidis
  • 3,130
  • 2
  • 20
  • 23
0

One possible solution is to completely disregard the affix-bottom situation by setting bottom: null like so:

$("#my-selector").affix({
    offset: {
        top: $("#some-thing").outerHeight(true),
        bottom: null
    }
});
Chris Barr
  • 29,851
  • 23
  • 95
  • 135
0

Just replace the code segment for affix in bootstrap as given below.

This goes -

this.$element.offset({ top: document.body.offsetHeight - offsetBottom - this.$element.height() }) 

and this replaces it

this.$element.offset({ top: scrollHeight - offsetBottom - this.$element.height() })

Cheers.

Rohan
  • 3,296
  • 2
  • 32
  • 35
0

Set position: absolute for .affix-top & .affix-bottom, and position: fixed for .affix.

According to official doc

Stream Huang
  • 326
  • 1
  • 3
  • 7
0

I made this work via some trial and error process but this is what worked for me.

JS

$(document).ready(function() {
var SidebarTop = $('#your-affix').offset().top - x; //adjust x to meet your required positioning
    SidebarBottom = $('.site-footer').outerHeight() + x;

    $('#your-affix').affix({
        offset: {
        top: SidebarTop,
        bottom: SidebarBottom
        }
    });
});

CSS

#your-affix.affix {
    top: boo px; //the px from top of the window where you want your sidebar
                 //be when you scroll
    width: foo px; //your sidebar width
  }
#your-affix.affix-bottom {
    position: absolute;
    width: foo px; //your sidebar width
}

After this code my sidebar become "unpinned" when it reaches the footer. And stops jumping to the top of the site when it reaches the bottom offset option.

Oksana Romaniv
  • 1,569
  • 16
  • 18