What is the process for closing the side menu by clicking on the dark area?

I created a basic side navigation menu. When you resize the window to a smaller size, a red square will appear. If you click on this red square, the menu will open.

The menu opens correctly, but I want it to close when I click on the dark area instead of the X button. I tried adding a "click" event listener to the body and removing the "is-open" class, but that didn't work. After spending some time troubleshooting, I decided to seek suggestions here.

"use strict";

const menuToggle = document.querySelector(".menu-toggle");
const menuClose = document.querySelector(".menu-close");
const nav = menuToggle.parentElement;

menuToggle.addEventListener("click", event => {
  event.preventDefault();
  nav.classList.add("is-open");
  document.body.style.backgroundColor = "rgba(0,0,0,0.5)";
});

menuClose.addEventListener("click", event => {
  event.preventDefault();
  menuToggle.nextElementSibling.style.width = null;
  document.body.style.backgroundColor = null;
  nav.classList.remove("is-open");
});
:root {
  box-sizing: border-box;
}

*, *::before, *::after {
  box-sizing: inherit;
}

body {
  margin: 0;
  padding: 0;
}

.menu-toggle {
  width: 40px;
  height: 40px;
  border: 1px solid red;
  cursor: pointer;
}

.menu-container {
  position: absolute;
  background: lightskyblue;
  height: 100vh;
  width: 0;
  transition: width 0.4s ease-in-out;
  top: 0;
  left: 0;
  overflow: auto;
  z-index: 1;
}

.menu-close {
  position: absolute;
  right: 1em;
}

.nav-menu {
  list-style: none;
  padding-left: 0;
  margin: 50px 0 0 0;
}

.nav-menu > li + li {
  border-top: 1px solid #fff;
}

.nav-menu > li > a {
  display: block;
  color: #000;
  padding: 0.8em 1em;
  font-size: 1.1rem;
  text-decoration: none;
  text-transform: uppercase;
}


.nav.is-open .menu-container {
  width: 200px;
}

.menu-close::before {
  content: "\00d7";
  font-size: 2.6rem;
}

/*@media screen and (min-width: 37.5em) {*/
@media screen and (min-width: 40.5em) {
  body {
    background: #fff !important;
  }

  .menu-toggle {
    display: none;
  }

  .nav.is-open .menu-container {
    width: auto;
    height: auto;
  }

  .menu-container {
    position: initial;
    height: auto;
    width: auto;
    overflow: hidden;

  }

  .menu-close {
    display: none;
  }

  .nav-menu {
    display: flex;
    position: static;
    justify-content: center;
    margin: 0;
  }

  .nav-menu > li {
    margin-left: 1em;
  }

  .nav-menu > li + li {
    border-top: initial;
  }
}
<!DOCTYPE html>
<html lang="en-US">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <link rel="stylesheet" href="responsive.css">
  <title>Responsive Navigation Menu</title>
</head>
<body>
<nav class="nav">
  <div class="menu-toggle">
    <span class="menu-toggle__linecenter"></span>
  </div>

  <div class="menu-container">
    <span class="menu-close"></span>
    <ul class="nav-menu">
      <li><a href="#">Home</a></li>
      <li><a href="#">Menu Item 1</a></li>
      <li><a href="#">Menu Item 2</a></li>
      <li><a href="#">Menu Item 3</a></li>
    </ul>
  </div>
</nav>

<main>
  <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit.
    Accusamus accusantium aliquid consequatur facere illum
    incidunt magnam magni maiores nam neque numquam omnis
    perferendis porro quae quibusdam, quos sed tenetur ullam.
  </p>
</main>
<script src="responsible.js"></script>
</body>
</html>

Answer №1

It seems like the best approach would be to utilize document instead of body for handling the click event. By using event.stopImmediatePropagation() instead of event.preventDefault(), you can allow the first click but prevent the second one. Additionally, ensure that the click event only triggers when clicking anywhere except the menu.

Please note that I had to remove your media query as it was causing issues with displaying the toggle-menu.

"use strict";

const menuToggle = document.querySelector(".menu-toggle");
const menuClose = document.querySelector(".menu-close");
const nav = menuToggle.parentElement;

menuToggle.addEventListener("click", event => {
  event.stopImmediatePropagation();
  
  nav.classList.add("is-open");
  document.body.style.backgroundColor = "rgba(0,0,0,.5)";
});

document.addEventListener("click", event => {
  if (nav.classList.contains("is-open") && !event.target.classList.contains("nav-menu")) {
    menuToggle.nextElementSibling.style.width = null;
    document.body.style.backgroundColor = "#fff";
    nav.classList.remove("is-open");
  }
});
:root {
  box-sizing: border-box;
}

*, *::before, *::after {
  box-sizing: inherit;
}

body {
  margin: 0;
  padding: 0;
}

.menu-toggle {
  width: 40px;
  height: 40px;
  border: 1px solid red;
  cursor: pointer;
}

.menu-container {
  position: absolute;
  background: lightskyblue;
  height: 100vh;
  width: 0;
  transition: width 0.4s ease-in-out;
  top: 0;
  left: 0;
  overflow: auto;
  z-index: 1;
}

.menu-close {
  position: absolute;
  right: 1em;
}

.nav-menu {
  list-style: none;
  padding-left: 0;
  margin: 50px 0 0 0;
}

.nav-menu > li + li {
  border-top: 1px solid #fff;
}

.nav-menu > li > a {
  display: block;
  color: #000;
  padding: 0.8em 1em;
  font-size: 1.1rem;
  text-decoration: none;
  text-transform: uppercase;
}


.nav.is-open .menu-container {
  width: 200px;
}

.menu-close::before {
  content: "\00d7";
  font-size: 2.6rem;
}

}
<nav class="nav">
  <div class="menu-toggle">
    <span class="menu-toggle__linecenter"></span>
  </div>

  <div class="menu-container">
    <span class="menu-close"></span>
    <ul class="nav-menu">
      <li><a href="#">Home</a></li>
      <li><a href="#">Menu Item 1</a></li>
      <li><a href="#">Menu Item 2</a></li>
      <li><a href="#">Menu Item 3</a></li>
    </ul>
  </div>
</nav>

<main>
  <p>
    Lorem ipsum dolor sit amet, consectetur adipisicing elit.
    Accusamus accusantium aliquid consequatur facere illum
    incidunt magnam magni maiores nam neque numquam omnis
    perferendis porro quae quibusdam, quos sed tenetur ullam.
  </p>
</main>

Answer №2

Check out the solution I provided for your request. A new div with a class hidden has been added next to the menu, and the script is set up to trigger a function when the event.target matches that div or the close button. Feel free to reach out if you have any questions.

const menuToggle = document.querySelector(".menu-toggle");
const menuClose = document.querySelector(".menu-close");
const menuActive = document.querySelector(".menu_active");
const nav = menuToggle.parentElement;

const log = console.log;

const app = {
  init: () => {
    app.menuToggle();
  },

  menuToggle: () => {
    menuToggle.addEventListener("click", event => {
      event.preventDefault();
      nav.classList.add("is-open");
      menuActive.classList.remove('hidden');
      app.closeMenu();
    });
    log('working')

  },
  closeMenu: () => {
    if (nav.classList.contains('is-open')) {

      document.addEventListener("click", event => {
        event.preventDefault();
        let closeMenu = event.target.className;
        if (closeMenu === 'menu_active' || closeMenu === 'menu-close') {
          //log(event.target.className)
          menuToggle.nextElementSibling.style.width = null;
          menuActive.classList.add('hidden');
          nav.classList.remove("is-open");
        }
      });
    }
  }

};


document.addEventListener('DOMContentLoaded', app.init())
:root {
  box-sizing: border-box;
}

*,
*::before,
*::after {
  box-sizing: inherit;
}

body {
  margin: 0;
  padding: 0;
}

.menu_active {
  background: rgba(0, 0, 0, 0.5);
  position: absolute;
  height: 100vh;
  width: calc(100vw - 200px);
  top: 0;
  right: 0;
}

.hidden {
  visibility: hidden;
  display: none;
}

.menu-toggle {
  width: 40px;
  height: 40px;
  border: 1px solid red;
  cursor: pointer;
}

.menu-container {
  position: absolute;
  background: lightskyblue;
  height: 100vh;
  width: 0;
  transition: width 0.4s ease-in-out;
  top: 0;
  left: 0;
  overflow: auto;
  z-index: 1;
}

.menu-close {
  position: absolute;
  right: 1em;
}
  
.nav-menu {
  list-style: none;
  padding-left: 0;
  margin: 50px 0 0 0;
}
  
.nav-menu > li+li {
  border-top: 1px solid #fff;
}
  
.nav-menu > li > a {
  display: block;
  color: #000;
  padding: 0.8em 1em;
  font-size: 1.1rem;
  text-decoration: none;
  text-transform: uppercase;
}
  
.nav.is-open .menu-container {
  width: 200px;
}

.menu-close::before {
  content: "\00d7";
  font-size: 2.6rem;
}


@media screen and (min-width: 40.5em) {
  body {
    background: #fff !important;
  }
  .menu-toggle {
    display: none;
  }
  .nav.is-open .menu-container {
    width: auto;
    height: auto;
  }
  .menu-container {
    position: initial;
    height: auto;
    width: auto;
    overflow: hidden;
  }
  .menu-close {
    display: none;
  }
  .nav-menu {
    display: flex;
    position: static;
    justify-content: center;
    margin: 0;
  }
  .nav-menu > li {
    margin-left: 1em;
  }
  .nav-menu > li+li {
    border-top: initial;
  }
}
<body>
  <nav class="nav">
    <div class="menu-toggle">
      <span class="menu-toggle__linecenter"></span>
    </div>

    <div class="menu-container">
      <span class="menu-close"></span>
      <ul class="nav-menu">
        <li><a href="#">Home</a></li>
        <li><a href="#">Menu Item 1</a></li>
        <li><a href="#">Menu Item 2</a></li>
        <li><a href="#">Menu Item 3</a></li>
      </ul>
    </div>
    <div class="menu_active hidden"></div>
  </nav>

  <main>
    <p>
      Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusamus accusantium aliquid consequatur facere illum incidunt magnam magni maiores nam neque numquam omnis perferendis porro quae quibusdam, quos sed tenetur ullam.
    </p>
  </main>
</body>

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Having trouble with transferring information from JQuery to PHP

Currently, I'm working on transmitting data from jQuery to PHP. Here's an excerpt of what I've done: var jsonArray = JSON.stringify(dataArray); $.ajax({ type: "POST", url: "addcar_details.php", ...

The art of simulating a service in unit tests for an AngularAMD application

I am working on an angular application using AngularAMD with require.js. I need to simulate a service as shown below: module(function ($provide) { $provide.service('ToolsService', function () { var toolsServiceMock = { som ...

How can I retrieve properties from a superclass in Typescript/Phaser?

Within my parent class, I have inherited from Phaser.GameObjects.Container. This parent class contains a property called InformationPanel which is of a custom class. The container also has multiple children of type Container. I am attempting to access the ...

Using `getJson` to parse the tree structure

I am currently working with a JSON file that contains order data. The structure of the JSON file is as follows: { "orders": [ {"name": "Peter", "email": "<a href="/cdn-cgi/l/email-protection" class="__cf_email__" data-cfemail="64140110011624050b0 ...

search for a specific value within a nested subfield of an asterisk star field in Firestore

Here is the data I have: { root: { _rEG: { fen: 'value' }, _AS: { fen: 'value' }, _BSSA: { fen: 'value' } } } I would like to query using where('root.*.fen', '==', 'value'). ...

What causes certain divs to protrude when the parent div has overflow:hidden property enabled?

Issue: I am facing difficulty aligning all elements within one div without any overflow issues. Even with the parent div set to overflow:hidden, some child divs are protruding out of the container. How can I resolve this problem? Example: http://jsfiddle. ...

Navigating the battleground between native and HTML5 mobile applications

As an experienced iOS developer with knowledge in Windows Phone and Android, I have observed the increasing popularity of HTML5 web-apps over native apps. This shift towards HTML5 development frustrates me, as a strong argument can be made for native app d ...

what is the process for creating a dynamic display slide in bxslider?

I am trying to create a flexible length display using bxSlider. Here is the code I have so far. JS var duration = $('ul > li > img').data("bekleme"); $(document).ready(function () { $('.bxslider').bxSlider({ ...

Tips for making a rounded bottom image slider with react-native?

Is there a way to design an image slider similar to this with rounded bottom images? ...

What is the best way to connect tags with their corresponding tag synonyms?

I'm currently developing a system where users can link tags to posts, similar to how it's done on SO. I'm facing some challenges when it comes to implementing tag synonyms. Let's take a look at the Tags table: | TagName | |-------- ...

Ext.ux.TDGi.iconMgr is a cutting-edge plugin designed specifically for the latest version of

Has anyone successfully migrated the Ext.ux.TDGi.iconMgr plugin to ExtJS 4? (http://tdg-i.com/44/extuxtdgiiconmgr...-icons-and-css) ...

Retrieve information from an XML document

I have some XML content that looks like this: <Artificial name="Artifical name"> <Machine> <MachineEnvironment uri="environment" /> </Machine> <Mobile>taken phone, test when r1 100m ...

In CSS3, utilize the calc() function to vertically center elements using viewport height (vh) and viewport

One of the most common cases involves using vh for setting the height of a div, and using vm for adjusting font size. Using CSS3 div.outer { height: 20vh; } div.inner { font-size: 3vw; height: auto; } div.inner2 { font-size: 2vw; } HTML - Scenario 1 <d ...

Send the selected option from the dropdown menu to a different page

I am in the process of designing a unique shopping cart system where, upon clicking on the "add to cart" link, both the selected quantity from the drop-down menu and the product ID are transmitted to the addCart page. While I have successfully managed to p ...

Achieving hover effects on <a> tags in CSS without using the href attribute

I'm looking to create a picture that changes when hovered over, and I've already achieved this using CSS by adjusting the z-index. However, I don't want users to be able to click on the image. To prevent this, I have removed the href from th ...

Guide: Building a Dropdown Form in Angular 2

I have a webpage with an HTML form that includes a button positioned above the form. I am interested in adding functionality to the button so that when it is clicked, a duplicate of the existing form will be added directly beneath it. This will allow for m ...

Placing the date of each blog post at the end of the excerpt

Currently, there is a grid section on the homepage of our site showcasing the two most recent blog posts. I am looking for a Javascript solution to relocate the date to the bottom of each block, outside of the border. For reference, you can visit the follo ...

Looking to modify the HTML layout in order to be compatible with a specific script

Utilizing a form validation script with required:true data-validation will stop the form from submitting and highlight invalid fields in red by adding "has-error" to the parent container when the user submits the form. In the code snippet below, "has-erro ...

When triggering the fireEvent.mouseOver event, it seems that document.createRange is not a valid

Having trouble using fireClick.mouseOver(tab) to test tooltip functionality on tab hover. Here's a snippet of the code: it('should handle change on hover of tab', () => { const {getByTestId, getByRole} = renderComponent('Dra ...

If the condition is true, apply a class with ng-class when ng-click is triggered

I am facing a situation similar to toggling where I am adding the class col-xs-6 to divide the page into two views. Initially, when I click, I set a variable value as true and in ng-class, I check for a condition to append the col-xs-6 class. Below is the ...