Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bootstrap 3.0 scrollspy responsive offsets

I'm trying to get Bootstrap's scrollspy to work reliably on a responsive site on which the top navbar's height changes according to the width of the media/browser. So instead of hardcoding the offset on the data-offset attribute I'm setting it dynamically through Javascript initialization like this:

$('body').scrollspy({ offset: 70, target: '#nav' });

For wide layouts (i.e. Bootstrap -lg) it works fine but for narrower layouts there seems to be an "accumulating" offset in effect. In other words, it works fine for the first section but then takes increasing pixels to activate the following ones (e.g. 90px for the next, 110px for the 3rd, etc.).

I tried manipulating the scrollspy object directly as mentioned in this answer: How do I set the offset for ScrollSpy in Bootstrap? but to no avail.

Can someone recommend a canonical way of implementing scrollspy in a responsive site where the offset varies according to the media width?

Additional Information:

I just analyzed the scrollspy object in both scenarios and it turns out that the list of offsets is different when it's initialized through data- attributes only vs. via JS. It seems like when I initialize it via JS the offsets array gets populated before some of BS responsive adjustments happen and therefore the heights are different. How can I trigger scrollspy's initialization after all the responsive stuff has run? Is there a hook/callback after BS is done adjusting the layout? Is JS even involved or is all the responsive stuff handled by CSS?

like image 692
Ike Avatar asked Aug 14 '13 23:08

Ike


1 Answers

I'm aware that the original question asks for BS3 but I did not find a correct and easy answer for BS4. Hence I'm now attaching my solution which works perfectly for me.

As of BS4 the configuration location changed. If you are searching for the correct spot to directly change the offset on the fly. Here it is: $('body').data('bs.scrollspy')._config.offset. Additionally if you want that the direct change takes effect. Call $('body').scrollspy('refresh'); afterwards. Now the offset is respected by scrollspy.

Based on this approach I wrote a little snippet which might help you in adapting the offset dynamically for a specific navigation container (e.g. BS4 navbar).

var initScrollSpy = function() {
  var navContainer = '#mainNav'; // your navigation container

  // initial bind of scrollspy
  var bindScrollSpy = function() {
      $('body').scrollspy({
          target: navContainer,
          offset: getOffset() // determine your offset
      });
  }

  // get your offset dynamically
  var getOffset = function() {
      return $(navContainer).outerHeight();
  };

  // update the offset but only if the container size changed
  var updateOffset = function() {
      var cfg = $('body').data('bs.scrollspy')._config;
      if(cfg.offset != getOffset()){
          cfg.offset = getOffset();
          $('body').scrollspy('refresh');
      }
  }

  bindScrollSpy(); // bind scrollspy 
  $(window).resize(updateOffset); // react on resize event
  $(".collapse").on('shown.bs.collapse', updateOffset); // react on BS4 menu shown event (only on mobile). You might omit this line if your navigation has no change in height when opened on mobile
};

initScrollSpy(); // finally call snippet

I attached the explanations of the snippet within the code. But generally I bind the scrollspy offset as you normally would do. Additionally, we have two events. One that reacts on window resize and one that reacts on navbar fully shown. In case of both events I check if the offset value differs from the desired one and update it directly within scrollspy and that's it.

like image 160
thex Avatar answered Oct 20 '22 18:10

thex