Search code examples
javascriptcssoverflowsubmenu

How can I display the correct scrollbar when dealing with nested overflows?


When you click the Services menu item, everything works properly, but the parent's scrollbar is showing on top of the child's scrollbar. I'm still able to scroll properly inside of the child menu, however that's not emulated with the visible scrollbar.

When I remove overflow: auto; from the .sidebar container, the child menu scroll works properly, but the parent menu's does not.

How can I make sure the visible scrollbar is for the menu that is active?

( function( $ ) {

	var sidebar = $( '.sidebar' );
  var menu = sidebar.find( '> ul, > div > ul' );

	// submenus
  ( function() {
  
  	if ( ! sidebar.length || ! menu.children().length || ! menu.length ) {
    	return;
    }
    
    var hasChildren = menu.find( '.has-children > a' );
    var goBack = menu.find( '.go-back' );
    var toggleSub = function( e ) {
    	e.preventDefault();
      var _this;
      
      if ( ! $( e.target ).closest( '.go-back' ).length ) {
      	_this = $( this );
      } else {
      	_this = $( this ).closest( '.sub-menu' ).prev();
      }
      
      _this.find( '~ .sub-menu' ).toggleClass( 'active' );
      _this.closest( 'ul' ).toggleClass( 'slide-out' );
    };
    
    hasChildren.on( 'click', toggleSub );
    goBack.on( 'click', toggleSub );
  
  } ) ();

} ) ( jQuery );
html, body {
  height: 100%;
  background: #f5f5f5;
  font-family: Arial, Helvetica, Helvetica Neue, sans-serif;
}

.sidebar {
  position: fixed;
  top: 0;
  left: 0;
  width: 280px;
  height: 100%;
  background: #fe6570;
  box-shadow: 2px 0px 4px 0px rgba(50,50,50,0.5);
  overflow: auto;
}

.sidebar ul li a {
  display: block;
  padding: 0 1rem;
  color: rgba(255, 255, 255, 0.8);
  line-height: 3.5rem;
  text-transform: capitalize;
  text-decoration: none;
  border-bottom: 1px solid rgba(230, 230, 230, 0.2);
  transition: transform 0.3s ease-in-out;
}

.sidebar ul .has-children > a {
  position: relative;
}

.sidebar ul .has-children > a:after,
.sidebar .go-back > a:before {
  content: '›';
  position: absolute;
  font-size: 1.5rem;
  right: 1rem;
}

.sidebar .go-back > a:before {
  content: '‹';
  position: relative;
  right: initial;
  left: 0;
  margin-right: 10px;
}

.sidebar .sub-menu {
  position: fixed;
  z-index: 1;
  top: 0;
  left: 0;
  height: 100%;
  width: 280px;
  background: #fe8065;
  transition: transform 0.3s ease-in-out;
  transform: translateX(-100%);
}

.sidebar ul {
  overflow-y: auto;
  list-style: none;
  padding: 0;
}

.sidebar .sub-menu.active {
  transform: translateX(0);
}

.sidebar .slide-out > li > a {
  transform: translateX(100%);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="sidebar">
  <ul>
    <li><a href="#">Home</a></li>
    <li><a href="#">About Us</a></li>
    <li class="has-children">
      <a href="#">Services</a>
      <ul class="sub-menu">
        <li class="go-back"><a href="#">Services</a></li>
        <li><a href="#">Consultations</a></li>
        <li><a href="#">Design &amp; Planning</a></li>
        <li><a href="#">Service Item 1</a></li>
        <li><a href="#">Service Item 2</a></li>
        <li><a href="#">Service Item 3</a></li>
      </ul>
    </li>
    <li><a href="#">Photo Gallery</a></li>
    <li><a href="#">Resources</a></li>
    <li><a href="#">Find a Location</a></li>
    <li><a href="#">Contact Us</a></li>
  </ul>
</div>


Solution

  • Simple, change the .sidebar element from overflow: auto; to overflow: hidden when the Services submenu is opened, like this:

    ( function( $ ) {
    
    	var sidebar = $( '.sidebar' );
      var menu = sidebar.find( '> ul, > div > ul' );
    
    	// submenus
      ( function() {
      
      	if ( ! sidebar.length || ! menu.children().length || ! menu.length ) {
        	return;
        }
        
        var hasChildren = menu.find( '.has-children > a' );
        var goBack = menu.find( '.go-back' );
        var toggleSub = function( e ) {
        	e.preventDefault();
          var _this;
          
          if ( ! $( e.target ).closest( '.go-back' ).length ) {
          	_this = $( this );
          } else {
          	_this = $( this ).closest( '.sub-menu' ).prev();
          }
          
          _this.find( '~ .sub-menu' ).toggleClass( 'active' );
          _this.closest( '.sidebar' ).toggleClass( 'overflow-off' );
          _this.closest( 'ul' ).toggleClass( 'slide-out' );
        };
        
        hasChildren.on( 'click', toggleSub );
        goBack.on( 'click', toggleSub );
      
      } ) ();
    
    } ) ( jQuery );
    html, body {
      height: 100%;
      background: #f5f5f5;
      font-family: Arial, Helvetica, Helvetica Neue, sans-serif;
    }
    
    .sidebar {
      position: fixed;
      top: 0;
      left: 0;
      width: 280px;
      height: 100%;
      background: #fe6570;
      box-shadow: 2px 0px 4px 0px rgba(50,50,50,0.5);
      overflow: auto;
    }
    
    .overflow-off {
      overflow: hidden;
    }
    
    .sidebar ul li a {
      display: block;
      padding: 0 1rem;
      color: rgba(255, 255, 255, 0.8);
      line-height: 3.5rem;
      text-transform: capitalize;
      text-decoration: none;
      border-bottom: 1px solid rgba(230, 230, 230, 0.2);
      transition: transform 0.3s ease-in-out;
    }
    
    .sidebar ul .has-children > a {
      position: relative;
    }
    
    .sidebar ul .has-children > a:after,
    .sidebar .go-back > a:before {
      content: '›';
      position: absolute;
      font-size: 1.5rem;
      right: 1rem;
    }
    
    .sidebar .go-back > a:before {
      content: '‹';
      position: relative;
      right: initial;
      left: 0;
      margin-right: 10px;
    }
    
    .sidebar .sub-menu {
      position: fixed;
      z-index: 1;
      top: 0;
      left: 0;
      height: 100%;
      width: 280px;
      background: #fe8065;
      transition: transform 0.3s ease-in-out;
      transform: translateX(-100%);
    }
    
    .sidebar ul {
      overflow-y: auto;
    }
    
    .sidebar .sub-menu.active {
      transform: translateX(0);
    }
    
    .sidebar .slide-out > li > a {
      transform: translateX(100%);
    }
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
    <div class="sidebar">
      <ul>
        <li><a href="#">Home</a></li>
        <li><a href="#">About Us</a></li>
        <li class="has-children">
          <a href="#">Services</a>
          <ul class="sub-menu">
            <li class="go-back"><a href="#">Services</a></li>
            <li><a href="#">Consultations</a></li>
            <li><a href="#">Design &amp; Planning</a></li>
            <li><a href="#">Service Item 1</a></li>
            <li><a href="#">Service Item 2</a></li>
            <li><a href="#">Service Item 3</a></li>
            <li><a href="#">Service Item 4</a></li>
            <li><a href="#">Service Item 5</a></li>
          </ul>
        </li>
        <li><a href="#">Photo Gallery</a></li>
        <li><a href="#">Resources</a></li>
        <li><a href="#">Find a Location</a></li>
        <li><a href="#">Contact Us</a></li>
      </ul>
    </div>

    A few ways to accomplish this, in the example above I've added

    _this.closest( '.sidebar' ).toggleClass( 'overflow-off' );
    

    to the toggleSub function, with the overflow-off class being:

    .overflow-off {
      overflow: hidden;
    }