3

I am trying to implement an affix in bootstrap. It shouldn't be a problem, but I want to make it responsive.

When a page has resized, some elements will hide at the top of the page, and because of change of width, I need to reset the affix and set to the new position.

I just search for it, and found an accepted solution, but it seems, it is not works.

This is what I've tried:

$(window).off('#myAffix');
$('#myAffix').removeData('bs.affix').removeClass('affix affix-top affix-bottom');
$('#myAffix').affix({
    offset: {
        top: function () {
            console.log('Offset top from: ' + $('.navbar').offset().top);
            return (this.top = $('.navbar').offset().top);
        }
    }
});

I am calling it on document.ready, and on window.resize events.

I've created a demo for it. This is the steps, how you can reproduce the problem:

  • When you open it, resize the reult window while there are 2 columns before the bordered affix, and reload the page. When you scroll down, you will see, the affix is starts to work when it need.

  • Resize the window while only the left column remains. Affix is works now also.

  • Resize the window again, while the right column appears. Now affix will trigger, when it reach the previous position, the height of the left column. That is bad.

It seems, my code is running, but has no effect on affix.

PS: Do not care about the position of the affix, I just want to fix, when / where it will be fixed.

jsFiddle here.

Community
  • 1
  • 1
vaso123
  • 12,347
  • 4
  • 34
  • 64

2 Answers2

9

In your sample, you have in the HTML the .affix-top class and the data-spy="affix" attribute.

<nav class="navbar scroll-menu affix-top" data-spy="affix" id="myAffix" style="border: 1px solid #f00;">
  <ul class="nav navbar-nav" style="font-size: 10px; background: #fff;">
    <li><a href="#section1">SECTION ONE</a></li>
    <li><a href="#section2">SECTION TWO</a></li>
  </ul>
</nav>

Those are not required since the .affix-top is added dynamically by the plugin when initialized and the data-spy="affix" attribute is also not required since you are initializing it with JavaScript.

So, your HTML would be:

<nav id="myAffix" class="navbar scroll-menu" style="border: 1px solid #f00;">
  <ul class="nav navbar-nav" style="font-size: 10px; background: #fff;">
    <li><a href="#section1">SECTION ONE</a></li>
    <li><a href="#section2">SECTION TWO</a></li>
  </ul>
</nav>

Also, in the documentation it specifies the following:

Use the affix plugin via data attributes or manually with your own JavaScript. In both situations, you must provide CSS for the positioning and width of your affixed content.

That means that you should add some CSS to set the width and position of the affixed div.

.affix {
  top: 40px;
  /* This is the desired offset where the affixed div will appear */
  width: 100%;
  /* This is the desired width where the affixed div will have */
}

With the above changes, the affix works as expected, both in full screen and small screen.


But then, I stumbled into a bug!! The bug appeared when loading in full screen, and then, resizing to a small screen.

When in small screen, the plugin remembered the offset properties of the full screen initialization. The bug is that the affix plugin binds a handler in the window.scroll event that is never removed.

That is a memory leak bug (found here), and a workaround for that is mentioned in an answer.

So, instead of calling:

$(window).off('#myAffix');

you should call the following for properly removing all events created by the previous initialization

$(window).off('.affix');

To sum up, your JS would be like:

$.initAffix = function() {
  $('#myAffix').affix({
    offset: {
      top: function() {
        console.log('Offset top from: ' + $('.navbar').offset().top);
        return (this.top = $('.navbar').offset().top);
      }
    }
  });
};

$.resetAffix = function() {
  console.log('resetAffix');
  $(window).off('.affix');
  $('#myAffix').removeData('bs.affix').removeClass('affix affix-top affix-bottom');
  $.initAffix();
};

$(window).on('resize', $.resetAffix);

$.initAffix();

Here is a working demo.

Community
  • 1
  • 1
Tasos K.
  • 7,979
  • 7
  • 39
  • 63
  • 1
    I must say that it's a bit frustrating that 12 hours after I gave my answer you took my code, changed it a bit, added explanations and wrote a longer answer only to "win" the bounty. Just saying. – Dekel Jun 20 '16 at 12:31
  • @Dekel My answer does not just contain your code, it has more steps and those steps sum up the research I made. The accepted answer the OP mentions, also contains your answer, and as the OP said, it didn't work. In any case, if the OP finds that your answer solves his issue, he should accept your answer. – Tasos K. Jun 20 '16 at 16:51
  • The answer the OP mentions doesn't contain my answer. The problem there was the differences between `$(window).off('#myAffix');` to `$(window).off('.affix');`. The whole solution is based on that. – Dekel Jun 20 '16 at 16:54
  • The accepted [answer](http://stackoverflow.com/a/23797510/2851870) (not the most upvoted one) contains the code `$(window).off('.affix');`, – Tasos K. Jun 20 '16 at 16:58
  • Ok guys, now I feel, I can not choose the right one. For whom should I give this bounty? @Dekel was faster, but you Tasos gave me a more explained answer with demo and pointed me to that bug. I just want to be fair, but in this situation I can not. I've tested both, and both works. Please, give me some advise. – vaso123 Jun 22 '16 at 10:00
  • 2
    @lolka_bolka Since both answers resolve your issue, you should award the bounty to the first answer. Also, imho, since my answer is complete you could consider accepting it. I understand that my opinion is a bit biased, so if this resolution does not seem fair to you, you could post a question on meta and get feedback from the community. At this point, I just want to point out, that I didn't take Dekel's answer, but my own research simply led me there. – Tasos K. Jun 22 '16 at 10:58
  • I'll do this. Thank you for your opinion. – vaso123 Jun 22 '16 at 11:41
2

There is only one change you need to do for your code to work as you wish, and this is to change the $(window).off('#myAffix'); to $(window).off('.affix');

This is the new function:

$.myFunction = function() {
    $(window).off('.affix')
    $('#myAffix').removeData('bs.affix').removeClass('affix affix-top affix-bottom');
    $('#myAffix').affix({
        offset: {
            top: function() {
                console.log('Offset top from: ' + $('.navbar').offset().top);
                return (this.top = $('.navbar').offset().top);
            }
        }
    });
};
Dekel
  • 60,707
  • 10
  • 101
  • 129