Bootstrap Scrollspy causes issues with Off-Canavas Menu
Asked Answered
C

3

10

UPDATE: To clear up some confusion I added a fiddle that demonstrates how it is supposed to work, but it is just missing the scrollspy: https://jsfiddle.net/kmdLg7t0/ How can I add the scrollspyto this fiddle so that the menu highlights when I'm on a specific section?

I created a fixed left menu that turns into an off-canvas menu at <992px in browser width for tablet and mobile. When I select the anchor link on a browser width >992px it closes the menu and navigates to the anchor link section.

Custom JQuery Code: This is my custom jQuery code that closes the Off-Canvas Menu when I click on an anchor link:

// close off-canvas menu and navigate to anchor
$('.navmenu-nav li a').on('click', function() {
  $('body').removeClass('bs.offcanvas');
});

PROBLEM:

I decided to add a bootstrap offscrollspy and it works as intended after the browser width is greater than 992px, but when I resize the browser width to less than 992px this interferes with the Custom Jquery Code to close the menu and navigate to the anchor link.

Here's the Fiddle:

Bootstrap ScrollSpy causes issue with Off Canvas Menu and JQuery Code

My GUESS: I'm guessing the solution to this problem is to use jquery or javascript to prevent or remove the data-target=".navmenu" from activating when my screen is less than the <992px. Or we can find a way to only activate the scrollspy after >992px. I'm currently trying to figure this out, but I need someone who is a true expert in jquery to solve this dilemma.

Pre-Requisite:

  • Bootstrap.min.css
  • Bootstrap.min.js
  • jasny-bootstrap.css
  • jasny-bootstrap.js

JS:

$(document).ready(function () {
  toggleOffcanvas($(window).width() <= 992);
});

// simulate modal opening
$('.nav-link').click(function() {
  if ($(window).width() > 992) {
    $('.backdrop').hide(0, false);
  }

    $('#navToggle').click();
});

$('.navmenu').on('show.bs.offcanvas', function() {
    if ($(window).width() <= 992) {
        $('.backdrop').fadeIn();
  }
});

$('.navmenu').on('hide.bs.offcanvas', function() {
    if ($(window).width() <= 992) {
        $('.backdrop').fadeOut();
  }
});

// close modal on resize
$(window).resize(function() {
  if ($(window).width() > 992) {
    $('.backdrop').hide(0, false);
    $('body').removeClass('bs.offcanvas');
  }

  toggleOffcanvas($(window).width() <= 992);
});

// switch active navigation link onclick
$('.nav a').on('click', function() {
  $('.nav').find('.active').removeClass('active');
  $(this).parent().addClass('active');
});

// close modal when navigating to anchor
$('.navmenu-nav li a').on('click', function() {
  $('body').removeClass('bs.offcanvas');
});

function toggleOffcanvas(condition) {
    if (!! condition) {
    $('.nav-link').attr('data-toggle', 'offcanvas');
  } else {
    $('.nav-link').removeAttr('data-toggle');
  }
}

html:

<body data-spy="scroll" data-target="#myScrollspy" data-offset="50">


<div class="backdrop"></div>

<div id="myScrollspy" class="navmenu navmenu-default navmenu-fixed-left offcanvas-sm colornav ">
<a href="#" class="close" data-toggle="offcanvas" data-target=".navmenu">&times;</a>
 <a id="navToggle" class=""><span></span></a>
  <h4 class="navmenu-brand visible-md visible-lg visible-sm visible-xs" href="#">2017</h4>
  <ul class="nav navmenu-nav">
    <li class="active"><a class="nav-link" data-toggle="offcanvas" data-target=".navmenu" href="#january">Enero</a></li>
    <li><a class="nav-link" data-toggle="offcanvas" data-target=".navmenu" href="#february">Msrs</a></li>
    <li><a class="nav-link" href="http://www.jasny.net/bootstrap/examples/navmenu-reveal/">Jupiter</a></li>
    <li><a class="nav-link" href="http://www.jasny.net/bootstrap/examples/navbar-offcanvas/">Off canvas navbar</a></li>
  </ul>

</div>

<div class="navbar navbar-default navbar-fixed-top navbar-preheader">
  <button type="button" class="navbar-toggle" data-toggle="offcanvas" data-target=".navmenu">
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
    <span class="icon-bar"></span>
  </button>
  <a class="navbar-brand" href="#">navbar brand</a>



</div>

<div class="container">
  <div class="page-header">
    <h1>Navmenu Template</h1>
  </div>
  <p class="lead">This example shows the navmenu element. If the viewport is <b>less than 992px</b> the menu will be placed the off canvas and will be shown with a slide in effect.</p>
  <p>Also take a look at the examples for a navmenu with <a href="http://www.jasny.net/bootstrap/examples/navmenu-push">push effect</a> and <a href="http://www.jasny.net/bootstrap/examples/navmenu-reveal">reveal effect</a>.</p>
  <p class="space"></p>
  <p id="january">January</p>
  <p id="february">February</p>
</div><!-- /.container -->
</body>

CSS:

html, body {
  height: 100%;
}
body {
  padding: 50px 0 0 0;
}
.space {padding-bottom:900px;}

.backdrop {
  background: rgba(0, 0, 0, 0.5);
  position: fixed;
  top: 0;
  bottom: 0;
  width: 100vw;
  height: 100vh;
  z-index: 1040;
  display: none;
}

.navbar-fixed-top {

  background:#fff!important;
}


.navbar {
  display: block;
  text-align: center;
}
.navbar-brand {
  display: inline-block;
  float: none; 
}
.navbar-toggle {
  position: absolute;
  float: left; 
  margin-left: 15px;
}

.container {
  max-width: 100%;
}

@media (min-width: 1px) {
  .navbar-toggle {
    display: block !important; background:none!important;  border:none !important; color:#f90 !important;
  }
}

@media (min-width: 992px) {
  body {
    padding: 30px 0 0 300px;
  }
  .navmenu {
    padding-top: 0; 
  }

.navbar-toggle {display:none!important;}
.close {display:none}


.navmenu-fixed-left {
  z-index:0;
  top: 48px;
  bottom: 0; background:#fff!important;
}

}

    .navbar-default .navbar-toggle .icon-bar{
       background-color:#333;
    }


.close {margin-right:10px; margin-top:10px;}

@media (max-width:991px) {



.navmenu-fixed-left {
  z-index:1050;
  top: 0;
  bottom: 0; background:#fff!important;
}


}

    .backdrop {display:none}

    #january, #february {
      text-transform: uppercase;
      background-color: red;
      text-align: center;
      line-height: 90vh;
      font-size: 5em;
      height: 90vh;
      color: white;
    }

    #february {
      background-color: green;
    }
Chronaxie answered 2/2, 2017 at 14:36 Comment(2)
I don't fully understand what the problem is and what you're trying to do. Can you boil it down a little bit?Memoried
I'm trying to make the bootstrap scroll spy activate when the browser width is 992px or greater, but when you reduce the screen size to 991px width or less the bootstrap scroll-spy should be removed or it will in cause issues with my off-canvas menu.Chronaxie
F
1

The problem with the code is that data-target=".navmenu" on menu items breaks the scrollspy plugin. Basically, scrollspy makes the connection between menu item and an element on the page via either data-target property or href property. Here is a part of it's source code:

return `${selector}[data-target="${target}"],` +
       `${selector}[href="${target}"]`

Because of this you can't use data-target on menu links to close the menu. You can use javascript to close the menu instead.

Here is updated link's HTML:

<li class="active"><a class="nav-link" href="#january">Enero</a></li>
<li><a class="nav-link" href="#february">Msrs</a></li>

And all the javascript you need:

$(document).ready(function () {
  // Add the backdrop when menu is shown
  $('.navmenu').on('show.bs.offcanvas', function() {
    $('.backdrop').fadeIn();
  });

  // Remove the backdrop when menu is hidden
  $('.navmenu').on('hide.bs.offcanvas', function() {
    $('.backdrop').fadeOut();
  });

  // Hide the menu on menu item click
  $('.nav-link').click(function() {
    if ($(window).width() <= 992) {
      $('.navmenu').offcanvas('toggle');
    }
  });

  // Remove the backdrop if window is resized over the breakpoint
  $(window).resize(function () {
    if ($(window).width() > 992) {
      $('.backdrop').fadeOut();
    }
  });
});

A complete working example:

https://jsfiddle.net/kmdLg7t0/5/

Finally you have to remove all href="#" from link elements where they are not necessary. For example close menu button will take you back to # even if you have navigated to #january.

So things I did in total:

  • removed data- attributes from links
  • closing menu with javascript on link click
  • removed unnecessary href=# from links

Everything else is handled by plugins themselves.

Forayer answered 11/2, 2017 at 12:9 Comment(5)
It's almost perfect but there's an issue that wasn't there before. When you click on the off-canvas menu, and resize the browser width to >992px, and then resize the browser again to <992px the black overlay pops up without the off-canvas menu: i.is.cc/1XBb6tVg.pngChronaxie
@Chronaxie you can try and create a workaround via window.resize event. If I were you I wouldn't worry too much about browser resizing. You as a developer do that a lot, but most users will rarely resize their browser windows.Forayer
The original windows resize workaround from my original fiddle: jsfiddle.net/kmdLg7t0 doesn't work on the new fiddle. Can you please show me how to fix this problem? I will add an extra bounty when you update your answer with the fiddle since the original bounty expired.Chronaxie
@Chronaxie I've updated the fiddle. jsfiddle.net/kmdLg7t0/5 Basically on window resize it checks if the window is over the breakpoint and if it is, it removes the backdrop, since we should never have a backdrop on a wider screenForayer
Perfect! Thank you for your answer, and for shortening my js code. :)Chronaxie
M
1

By tying the scroll spy to a class, you can then toggle said class as needed. In addition, make sure to run the function once on page load to set initial state.

$('body').scrollspy({ target: '.scroll-spy' });

toggleScrollSpy($(window).width() <= 992);

// close modal on resize
$(window).resize(function() {
  if ($(window).width() > 992) {
    $('.backdrop').hide(0, false);
    $('body').removeClass('bs.offcanvas');
  }

  toggleScrollSpy($(window).width() <= 992);
});

function toggleScrollSpy(condition) {
  if (!!condition) {
    $('#myScrollspy').addClass('scroll-spy');
  } else {
    $('#myScrollspy').removeClass('scroll-spy');
  }
}
Memoried answered 6/2, 2017 at 17:28 Comment(6)
Where do I add the class on the html? I tried: <div id="myScrollspy" class="scrollspy"Chronaxie
You don't, js does it for you.Memoried
It's not working, the off-canvas menu doesn't close and navigate to the anchor link: jsfiddle.net/mchwy9LxChronaxie
Pay attention to errors in console. Fiddle wraps everything into a document load event as it is, so that initial block in your js was unnecessary. I also changed the logic in function at the very end from !! to !. The logic works now, you can see the class being toggled on #myScrollspy during window resize. The rest of your js is a bit of mystery to me.Memoried
When I removed the "!" and left only one "!" It causes an issue that won't navigate to the anchor link properly on a screen width greater than 992px. Can you update the fiddle? Whatever you told me to do is not workingChronaxie
The fiddle doesn't work: The anchor link should highlight the menu and navigate to the desired section when I click on it, not just when I scroll by it, And when the screen size is less than 992px the off screen menu should close. Check the original fiddle without the scrollspy: jsfiddle.net/kmdLg7t0Chronaxie
L
1

I would simply say when you animate or resize web page, make sure your coordinates top, left and height, width are carefully calculated. Because if they there is any change during resize it will show undesired positions. So its always good idea to examine coordinates and then alter dynamically them as the need arises.

Lowlife answered 10/2, 2017 at 2:39 Comment(0)
F
1

The problem with the code is that data-target=".navmenu" on menu items breaks the scrollspy plugin. Basically, scrollspy makes the connection between menu item and an element on the page via either data-target property or href property. Here is a part of it's source code:

return `${selector}[data-target="${target}"],` +
       `${selector}[href="${target}"]`

Because of this you can't use data-target on menu links to close the menu. You can use javascript to close the menu instead.

Here is updated link's HTML:

<li class="active"><a class="nav-link" href="#january">Enero</a></li>
<li><a class="nav-link" href="#february">Msrs</a></li>

And all the javascript you need:

$(document).ready(function () {
  // Add the backdrop when menu is shown
  $('.navmenu').on('show.bs.offcanvas', function() {
    $('.backdrop').fadeIn();
  });

  // Remove the backdrop when menu is hidden
  $('.navmenu').on('hide.bs.offcanvas', function() {
    $('.backdrop').fadeOut();
  });

  // Hide the menu on menu item click
  $('.nav-link').click(function() {
    if ($(window).width() <= 992) {
      $('.navmenu').offcanvas('toggle');
    }
  });

  // Remove the backdrop if window is resized over the breakpoint
  $(window).resize(function () {
    if ($(window).width() > 992) {
      $('.backdrop').fadeOut();
    }
  });
});

A complete working example:

https://jsfiddle.net/kmdLg7t0/5/

Finally you have to remove all href="#" from link elements where they are not necessary. For example close menu button will take you back to # even if you have navigated to #january.

So things I did in total:

  • removed data- attributes from links
  • closing menu with javascript on link click
  • removed unnecessary href=# from links

Everything else is handled by plugins themselves.

Forayer answered 11/2, 2017 at 12:9 Comment(5)
It's almost perfect but there's an issue that wasn't there before. When you click on the off-canvas menu, and resize the browser width to >992px, and then resize the browser again to <992px the black overlay pops up without the off-canvas menu: i.is.cc/1XBb6tVg.pngChronaxie
@Chronaxie you can try and create a workaround via window.resize event. If I were you I wouldn't worry too much about browser resizing. You as a developer do that a lot, but most users will rarely resize their browser windows.Forayer
The original windows resize workaround from my original fiddle: jsfiddle.net/kmdLg7t0 doesn't work on the new fiddle. Can you please show me how to fix this problem? I will add an extra bounty when you update your answer with the fiddle since the original bounty expired.Chronaxie
@Chronaxie I've updated the fiddle. jsfiddle.net/kmdLg7t0/5 Basically on window resize it checks if the window is over the breakpoint and if it is, it removes the backdrop, since we should never have a backdrop on a wider screenForayer
Perfect! Thank you for your answer, and for shortening my js code. :)Chronaxie

© 2022 - 2024 — McMap. All rights reserved.