How to get dynamic data-offset values for Bootstrap 3 affix method
Asked Answered
R

7

32

I would like to use the Affix method described in Bootstraps documentation (http://getbootstrap.com/javascript/#affix), however the navbar I would like to fix to the top of the page after it scrolls to it can have different offset values depending upon the content above it.

Here's an example of the navbar:

<div class="navbar navbar-default" data-spy="affix" data-offset-top="200">
  <ul class="nav navbar-nav">
    <li class="active"><a href="#">Link</a></li>
    <li><a href="#">Link 1</a></li>
    <li><a href="#">Link 2</a></li>
  </ul>
</div>

As you can see, the data-offset-top is currently set at 200. This works fine if the content above is 200px tall, but the content above is dynamic and so the height above this navbar isn't always the same. How can I make the vale for data-offset-top be dynamic?

I'm guessing I'll have to use the javascript way of doing it but I'm nit sure.

Raphaelraphaela answered 9/9, 2013 at 16:3 Comment(1)
I needed a way to re-calculate the value on page resize and also to add a space holder so that affixing runs smooth e.g. no page jumping when the contents gets affixed. The solution -> https://mcmap.net/q/454331/-bootstrap-affix-dynamically-on-resizeCalculating
L
44

You could use jQuery to get the dynamic content height above the navbar. For example:

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

Working demo: http://bootply.com/69848

In some cases, offset.bottom must also be calculated to determine when to "un-affix" the element. Here's an example of affix-bottom

Leckie answered 9/9, 2013 at 16:39 Comment(1)
use .outerHeight() instead of .height() to include the border and paddingCristacristabel
M
22

I was absolutely frustrated with the way this works, every time doing some custom calculation and building the offset off the the heights of the elements above the desired affix.

Here's affix at it's best.

The normal boostrap affix doesn't take the items natural offset top position into account, you have to manually pass it in as an attribute. This takes care of that, and accounts for the offset top changing on window resize.

var $attribute = $('[data-smart-affix]');
$attribute.each(function(){
  $(this).affix({
    offset: {
      top: $(this).offset().top,
    }
  })
})
$(window).on("resize", function(){
  $attribute.each(function(){
    $(this).data('bs.affix').options.offset.top = $(this).offset().top
  })
})

I also posted this on code review.

Don't forget to add the data-smart-affix attribute to your affix element

Try this sample code: https://jsfiddle.net/NabiKAZ/qyrauogw/
(In this sample, the height of red section may be 100px or 200px or 300px in diffrence width of window, So need to diffrence data-offset-top when scrolled.)

Melisent answered 19/3, 2015 at 15:43 Comment(3)
you are my hero! Thanks man .. PS to others: When using this code, don't forget to add the "data-smart-affix" attribute to your affix element. ;)Throve
Best answer! nice done. this helped me to fix my problemTurves
There is a bug in your code, please use "$(this).data('bs.affix').options.offset.top" instead of "$(this).data('bs.affix').options.offset". Also may I suggest to use [data-offset-top-dynamic instead of [data-smart-affix]Calculating
F
3

ThomasReggi's data-smart-affix didn't work for me in some corner cases (when resizing the document, or when the affixed column was too tall). But it gave me a simple idea: Add an empty element before the affixed content, and use that to get the offset. Works well when resized.

While I was at it, I also set the width of the element to be it's natural width in the original parent. Here's my solution:

$('[data-affix-after]').each( function() {
  var elem = $(this);
  var parent_panel = elem.parent();
  var prev = $( elem.data('affix-after') );

  if( prev.length != 1 ) {
    /* Create a new element immediately before. */
    prev = elem.before( '<div></div>' ).prev();
  }

  var resizeFn = function() {
      /* Set the width to it's natural width in the parent. */
      var sideBarNavWidth = parent_panel.width()
          - parseInt(elem.css('paddingLeft'))
          - parseInt(elem.css('paddingRight'))
          - parseInt(elem.css('marginLeft'))
          - parseInt(elem.css('marginRight'))
          - parseInt(elem.css('borderLeftWidth'))
          - parseInt(elem.css('borderRightWidth'));
      elem.css('width', sideBarNavWidth);

      elem.affix({
          offset: {
              top: prev.offset().top + prev.outerHeight(true),
              bottom: $('body>footer').outerHeight(true)
          }
      });
  };

  resizeFn();
  $(window).resize(resizeFn);
});

The HTML that goes with it is:

<div id='before-affix'></div><!-- leave this empty -->
<div data-affix-after='#before-affix'>
    Put content to affix here.
</div>

or

<div data-affix-after='#create'><!-- any ID that doesn't exist -->
    Put content to affix here.
</div>

The required CSS is:

.affix { top: 0px },
.affix-bottom { position: absolute; }

If you want to disable affix (e.g. when the right column is stacked), then use the CSS:

.affix, .affix-bottom { position: static; }

inside the appropriate media query.

GI

Fontenot answered 4/9, 2015 at 12:38 Comment(0)
T
1

I suggest you to add wrapper around affixed div - to avoid the "scroll jump" with affixed navbar. Thomas posted nice solution, but it doesn't include colapsed space after affix and it's not working in 2.x boostrap version which I must use. So i decided to write completly new implementation of affix. It doesn't need boostrap, just jquery. It's my first javascript, so please have mercy :) but maybe it will help somebody. It's always nice to have solution without a bootstrap

function myaffix() {

var affixoffset = $('.navbar').offset().top

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

$(window).scroll(function () {

    if ($(window).scrollTop() <= affixoffset) {
        $('.navbar').removeClass('affix');
    } else {
        $('.navbar').addClass('affix');
    }
});

};

$(document).ready(function () {

   myaffix();

   $(window).on("resize", function () {
       myaffix();
   });

});

You can find example here: http://jsfiddle.net/3ss7fk92/

Technical answered 16/4, 2015 at 16:54 Comment(0)
P
0

Skelly above is right and there is no better way to do so. The only possible solution with "native" bootstrap is to attach the affix options with a JavaScript call (as mentioned above in https://mcmap.net/q/447518/-how-to-get-dynamic-data-offset-values-for-bootstrap-3-affix-method).

It is especially not possible to use the data-offset attribute to achieve the same effect (even if this would be much more convenient, and even if the documentation for Affix at http://getbootstrap.com/2.3.2/javascript.html#affix raises hope one could do so).

So one can write

data-offset='{"top":42}'

but one is not allowed to write e.g.:

data-offset='{"top":$("#banner").height()}'

The reason is that the data attributes are parsed through the JQuery .data() API which only allows pure JSON values to be parsed, see https://api.jquery.com/data/.

Phillip answered 14/6, 2015 at 10:49 Comment(2)
Your answer is not dynamic though, just static.Raphaelraphaela
Yes - this is not a stand-alone answer but an addendum to Skelly's answer (where I'm not allowed to comment). I just tried to clarify that it is pointless trying to express the dynamic affix computation without using custom JavaScript on the page - what I've tried for multiple hours - including debugging the bootstrap and JQuery routines.Phillip
A
0

Thnx ThomasReggi for an idea. But may be more correct to write:

$(this).data('bs.affix').options.offset.top = $(this).offset().top

for those like me who needed a bottom value too that would write analogically.

BTW also I think wrap it in a decorator based on setTimeout (somewhat like debounce).

Am‚lie answered 20/9, 2015 at 21:59 Comment(0)
S
0

I was having a lot of trouble with this earlier, and for some reason I couldn't get any of the solutions other people had suggested to work. I'm relatively new to Bootstrap and JQuery, so I was probably doing something wonky I wasn't catching. Regardless, I found a solution that works really well, although it doesn't strictly speaking use the affix functionality as Bootstrap intends. I kept my #thisElement.affix {top: desiredFixedPosition;} CSS intact, but got rid of the data-spy and data-offset-top attributes from #thisElement's HTML tag. I then hard-coded this in JQuery:

var newAffix = function() {
    if($("otherElementsAboveThisElement").height() <= $(window).scrollTop()) {
        $("#thisElement").addClass("affix");
    } else {
        $("#thisElement").removeClass("affix");
    }
}

$(document).ready(function() {
    $(window).resize(newAffix);
    $(window).scroll(newAffix);
}

As far as I've tested, this works regardless of how much you resize the page or scroll, and if you get creative with how you call it in the $(document).ready() function you could even have the menu remain in the right position during height animations. As stated, this isn't technically a fully "Bootstrap approved" solution, but it integrates well enough into a Bootstrap site that it should work for some :)

Spiry answered 18/10, 2017 at 3:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.