0

I'm working on a page with a simple side nav that has a fixed position, set top: 30%. What I need to happen is at the bottom of the page I want to fade the menu out so it doesn't overlap the footer, the code below works but I think it is checking on scroll so much it takes to long to calculate when you scroll down fast.

Is there a faster/more lightweight way to calculate when to hide the side-nav? I'm not familiar with debouncing but would it help?

Elements:

.body-container-wrapper - total height of page
.footer-container-wrapper - total height of the footer that we want the nav to be hidden at
.internal-side-nav - the menu position: fixed, top: 30%, right: 0

Example Page: http://hsb1.hubspot.com/profile-page-template

Script:

<script type="text/javascript">
$(document).scroll(function () {
   var y = $(this).scrollTop();
   if (y < $('.body-container-wrapper').outerHeight() - $('.footer-container-   wrapper').outerHeight() - 400 ) {
    $('.internal-side-nav').fadeIn();
 } else {
    $('.internal-side-nav').fadeOut();
 }
});
</script>
simpson
  • 33
  • 6

2 Answers2

0

I hadn't heard of debounce so I had to look it up. It could potentially help, but that would be an extra plugin you'd have to include and maintain and it might not work exactly how you want (I didn't see anything indicating that it does "bunches or time frames", just seemed to be bunches, which means it might fire late on you).

Instead, what you could do is throttle it yourself with a little bit of timing.

var scrollTimeInterval = 200; // how often we allow the action to trigger, in ms.
var lastScrollTime = 0;
var scrollTimeoutId = null;
$(document).scroll(function () {
   var now = (new Date()).getTime();
   var dScrollTime = now - lastScrollTime;
   lastScrollTime = now;

   if (dScrollTime < scrollTimeInterval) {
        // Set a timeout so we catch the last one.
        scrollTimeoutId = setTimeout(function() { $(document).scroll(); }, scrollTimeInterval - dScrollTime);
        return; // too soon, so we'll skip
   }

   // Clear any potentially pending timeout.
   clearTimeout(scrollTimeoutId);

   var y = $(this).scrollTop();
   if (y < $('.body-container-wrapper').outerHeight() - $('.footer-container-   wrapper').outerHeight() - 400 ) {
    $('.internal-side-nav').fadeIn();
 } else {
    $('.internal-side-nav').fadeOut();
 }
});

With this, the scroll event simply won't do anything if it hasn't been a certain amount of time since it last triggered. To ensure we catch the last scroll event (the last one before they stopped scrolling), I added a timeout which will trigger the scroll event one last time. We also have to make sure to clear that if we handle another scroll event before it fires (so we don't double up on it).

You can control the time interval it allows with the first variable. If 200ms feels a bit sluggish, you can reduce it to 100ms or 50ms and see if that gives a better balance.

Hope that helps.

samanime
  • 25,408
  • 15
  • 90
  • 139
0

Dealing with scroll events, in certain circumstances, there's not much you can do.

Solution 1: setTimeout that cancels itself on each iteration

This method is most efficient I believe, but maybe not ideal for you, because there will still be 300ms in which the sidenav would visually overlap the footer before it fades out.

var scrollTimeout = false;
$(document).scroll(function(){
  clearTimeout(scrollTimeout);
  var _this = this; // for the setTimeout function to know what "this" is
  scrollTimeout = setTimeout(function(){
    // your original code block goes here
    // BUT, replace "this" with "_this"
  }, 300);
});

The above essentially only runs your code when the user has not scrolled for at least 300 milliseconds.

Solution 2: Just good old optimization (not too many tricks)

This solution should hide the sidenav immediately, but still runs on every scroll, just does less

var myScrollEvent = (function(){
  var $win = $(window);
  var $foot = $('.footer-container-wrapper');
  var footer_top = $foot.offset().top; // where on the page the footer begins
  var $nav = $('.internal-side-nav');
  var nav_height = $nav.height(); // maybe use outerHeight(true)?...who knows, only you
  var is_hidden = false;

  // this is the actual function we want to run on-scroll
  return function(){
    // jquery, even on fixed elements, still seems to account for scroll position
    // when calculating top offset value, below is the coordinate of the bottom of the nav
    var nav_bottom = $nav.offset().top + nav_height;

    // if the bottom coord of the nav is lower than the top coord of the footer
    if(nav_bottom > footer_top && !is_hidden){
      is_hidden = true;
      // hide it code
    }else if(is_hidden){
      is_hidden = false;
      // show it code
    }
  };

})();

$(window).scroll(myScrollEvent);

The idea here is to cache some variables and also do the calculation a slightly different way. Your way doesn't seem by any means wrong, but this is just an alternative. Note that with this method, we're assuming the nav height will never change.

You could always combine the two solutions if you'd like as well.

Also, note that I haven't done any browser-2-browser testing, so if there are any flaws, of course let me know.

anson
  • 4,156
  • 2
  • 22
  • 30
  • I like the direction of #2, I can't get it to work though. I'm looking at it now, thoughts @andbeyond ? – simpson Jan 30 '14 at 02:37
  • what exactly is going wrong? one thing not specified is you need the whole block inside your `$(document).ready()` callback for it to work correctly. Other than that, try consoling out variables in the code to see if the values are what you expect them to be – anson Jan 30 '14 at 17:31