Search code examples
htmlcsscss-selectorspseudo-classsiblings

How to affect other elements when menu item is clicked


I have created a custom non sticky sub menu that I want to appear when a certain menu item is clicked on. I'm having it scrolled to location when clicked and I've decided I want to control visibility with the height attribute. What is the correct way of doing this with CSS?


Also this is my first question ever. I appreciate tips on asking better questions.

The structure looks something like this

#submenu {
  height: 0;
  overflow: hidden;
}

.submenu-trigger:focus~#submenu {
  height: 134px !important;
}
<div id="wrapper">
  <header>
    <nav>
      <ul>
        <li><a>Item 1</a></li>
        <li><a>Item 2</a></li>
        <li><a class="submenu-trigger" href="#submenu">Item 3</a></li>
        <li><a>Item 4</a></li>
      </ul>
    </nav>
  </header>

  <!-- Placed outside to follow page scroll -->
  <section>
    <div id="submenu">
      <nav>
        <ul>
          <li><a>Item 1</a></li>
          <li><a>Item 2</a></li>
          <li><a>Item 3</a></li>
        </ul>
      </nav>
    </div>
  </section>

  <main>
    …
  </main>
</div>


Edit: It seems this isn't possible with CSS. How do I get the submenu to retract using JS when the main menu item is no longer in focus?

Edit 2: Implemented a variant of @biberman's suggestion at https://ensjotannklinikk.no/forside-wip/. Here's the differences in the live version:

  • "Behandlinger" = the menu item in question

  • .behandlinger-meny instead of #submenu

  • .behandlinger-item instead of .submenu-trigger

  • 55px instead of auto, to preserve CSS transition

    document.querySelector('.behandlinger-item').addEventListener('click', function() {
            var submenu = document.querySelector('.behandlinger-meny');
            submenu.style.height = '55px';
    });

It's really close, kudos to @biberman.

Only thing missing is

  • Having it stay open on every click (not toggle on and off). Figured myself.

  • Toggle off (back to height 0) when clicking anywhere else but .behandlinger-meny and .behandlinger-item @biberman strikes again.

  • This fix doesn't work on mobile. New question.

Thank you so much, Stack Overflow!


Solution

  • This can easily be done with js (in a script tag or a seperate js file). You just need an event listener for 'click' and a small function for changing the height:

    document.querySelector('.submenu-trigger').addEventListener('click', function() {
        var submenu = document.querySelector('#submenu');
        submenu.style.height = submenu.style.height == 'auto' ? 0 : 'auto';
    });
    #submenu {
        height: 0;
        overflow: hidden;
    }
    
    .submenu-trigger:focus ~ #submenu {
        height: 134px !important;
    }
    <div id="wrapper">
      <header>
        <nav>
          <ul>
            <li><a>Item 1</a></li>
            <li><a>Item 2</a></li>
            <li><a class="submenu-trigger" href="#submenu">Item 3</a></li>
            <li><a>Item 4</a></li>
          </ul>
        </nav>
      </header>
      
      <section>
        <div id="submenu">
          <nav>
            <ul>
              <li><a>Item 1</a></li>
              <li><a>Item 2</a></li>
              <li><a>Item 3</a></li>
            </ul>
          </nav>
        </div>
      </section>
      
      <main>
        …
      </main>
    </div>


    If you search a solution without extra javascript, may be you could reduce it to a minimum and do it inline. (it is better not to mix html structure and programcode, but it works):

    #submenu {
        height: 0;
        overflow: hidden;
    }
    <div id="wrapper">
      <header>
        <nav>
          <ul>
            <li><a>Item 1</a></li>
            <li><a>Item 2</a></li>
            <li><a class="submenu-trigger" href="#submenu" onclick="document.querySelector('#submenu').style.height = (document.querySelector('#submenu').style.height == 'auto') ? '0' : 'auto';">Item 3</a></li>
            <li><a>Item 4</a></li>
          </ul>
        </nav>
      </header>
      
      <section>
        <div id="submenu">
          <nav>
            <ul>
              <li><a>Item 1</a></li>
              <li><a>Item 2</a></li>
              <li><a>Item 3</a></li>
            </ul>
          </nav>
        </div>
      </section>
      
      <main>
        …
      </main>
    </div>


    If you want to close the menu when you click somewhere else (not .submenu-trigger and not #submenu) or when you press 'Esc', then you need two more event listeners. But this is not inline:

    var submenu = document.querySelector('#submenu');
    var menuTrigger = document.querySelector('.submenu-trigger');
    
    function isChild(item, parentItem) {
        while (item != undefined && item != null && item.tagName.toUpperCase() != 'BODY'){
            if (item == parentItem){
                return true;
            }
            item = item.parentNode;
        }
        return false;
    }
    
    menuTrigger.addEventListener('click', function() {
        submenu.style.height = 'auto';
    });
    
    document.querySelector('body').addEventListener('click', function(e) {
        if ( !isChild(e.target, menuTrigger) && !isChild(e.target, submenu) ) {
            submenu.style.height = 0;
        }
    });
    
    document.addEventListener('keyup', function(e) {
        if ( e.key == 'Escape' ) {
            submenu.style.height = 0;
        }
    });
    #submenu {
        height: 0;
        width: 100px;
        background-color: #ddd;
        overflow: hidden;
    }
    <div id="wrapper">
        <header>
            <nav>
                <ul>
                    <li><a>Item 1</a></li>
                    <li><a>Item 2</a></li>
                    <li><a class="submenu-trigger" href="#submenu">Item 3</a></li>
                    <li><a>Item 4</a></li>
                </ul>
            </nav>
        </header>
    
        <section>
        <div id="submenu">
            <nav>
                <ul>
                    <li><a>Item 1</a></li>
                    <li><a>Item 2</a></li>
                    <li><a>Item 3</a></li>
                </ul>
            </nav>
        </div>
        </section>
    
        <main>
            ...
        </main>
    </div>


    jQuery example:

    var submenu = $('#submenu');
    
    $('.submenu-trigger').on('click', function() {
      submenu.css('height', 'auto');
    });
    
    $('body').on('click', function(e) {
      if (!$(e.target).is('.submenu-trigger') &&
        !$(e.target).parents().is('#submenu')) {
        submenu.css('height', 0);
      }
    });
    
    $(document).on('keyup', function(e) {
      if (e.key == 'Escape') {
        submenu.css('height', 0);
      }
    });
    #submenu {
      height: 0;
      width: 100px;
      background-color: #ddd;
      overflow: hidden;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <div id="wrapper">
      <header>
        <nav>
          <ul>
            <li><a>Item 1</a></li>
            <li><a>Item 2</a></li>
            <li><a class="submenu-trigger" href="#submenu">Item 3</a></li>
            <li><a>Item 4</a></li>
          </ul>
        </nav>
      </header>
    
      <section>
        <div id="submenu">
          <nav>
            <ul>
              <li><a>Item 1</a></li>
              <li><a>Item 2</a></li>
              <li><a>Item 3</a></li>
            </ul>
          </nav>
        </div>
      </section>
    
      <main>
        ...
      </main>
    </div>