Twitter Bootstrap Affix - how to stick to bottom?
Asked Answered
C

10

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;
    }
}
Cralg answered 5/3, 2013 at 15:52 Comment(3)
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?Tetany
Not sure if there's a solution. This issue seems to have been completely ignored.Cyder
If you are using bootstrap 3, there is a bug that was fixed in master, but not released yet: github.com/twbs/bootstrap/commit/…Instructor
S
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
});
Strychninism answered 19/2, 2014 at 23:25 Comment(2)
This works well and you can use auto !important for the affix-bottom in the CSS instead of the jquery handler: bootply.com/PWvY5VyzSy Just another option.Conchiolin
Thanks man.. i know sticky exists nowdays, but needed this solution ;)Smalto
C
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

Conchiolin answered 11/3, 2013 at 13:40 Comment(5)
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.Cralg
@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.Cralg
@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.Instructor
@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.Instructor
Oh really? In that case, could you look at the fiddle demo I linked to and point out what's wrong with it?Cralg
I
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)
Instructor answered 2/6, 2013 at 23:31 Comment(0)
R
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.

Rarely answered 19/12, 2013 at 18:49 Comment(2)
so what's the solution to account for borders?Rectify
an incredibly good start to an answer, but without any hint of how to fix it. worst possible way to tease me.Gallonage
W
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.

Wagram answered 4/3, 2014 at 9:25 Comment(0)
S
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.

Straka answered 10/7, 2013 at 1:10 Comment(0)
C
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
    }
});
Cralg answered 10/12, 2013 at 16:58 Comment(0)
P
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.

Peyton answered 20/1, 2014 at 6:8 Comment(0)
V
0

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

According to official doc

Voile answered 3/8, 2016 at 8:54 Comment(0)
H
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.

Haymes answered 26/11, 2016 at 9:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.