javascript - 在元素外部单击时切换元素并移除其可见性 - JavaScript

标签 javascript events click toggle

我有两个带有子菜单的项目的导航。我目前有一个可以打开和关闭的类,单击时会显示这些子菜单。
我想要它,所以当我单击页面上的任何位置时,如果它们可见,它们就会消失。
目前我认为我的代码对于它目前实现的功能来说有点冗长,也许使用 e.target 会更好。点击时?
您当前可以通过单击任一菜单项来关闭和打开菜单(这包括再次单击可见菜单项)。
我想通过单击菜单项外部来删除“可见”类,我可以做一个简单的 document.addEventListener('click', function(e) {})如果它正在显示,则在整个文档上删除“可见”类,但这似乎不起作用。
注:我需要在不使用 blur 的情况下执行此操作事件监听器
密码笔:https://codepen.io/emilychews/pen/bGWVVpq

var menu_item_1 = document.getElementById('item-1'),
    menu_item_2 = document.getElementById('item-2'),
    sub_menu_item_1 = document.getElementById('sub-item-1'),
    sub_menu_item_2 = document.getElementById('sub-item-2')

if (menu_item_1) {
      menu_item_1.addEventListener('click', function(e){
        sub_menu_item_1.classList.toggle('visible')

       // hide submenu 2
        sub_menu_item_2.classList.remove('visible')
    }, false)
}

if (menu_item_2) {
      menu_item_2.addEventListener('click', function(e){
        sub_menu_item_2.classList.toggle('visible')

        // hide submenu 1
        sub_menu_item_1.classList.remove('visible')
    }, false)
}
body {
  display: flex;
  justify-content: center;
  margin: 0;
  height: 100vh;
  width: 100%;
}

header {
  margin-top: 2rem;
  display: flex;
  width: 50%;
  justify-content: space-evenly;
  align-items: center;
  padding: 1rem;
  background: red;
  height: 2rem;
}

.menu-item {
  position: relative;
  padding: 1rem;
  background: yellow;
  cursor: pointer;
}

.submenu {
  display: none; /* changes to 'block' with javascript */
  padding: 1rem;
  background: lightblue;
  position: absolute;
  top: 4rem;
  left: 0;
  width: 6rem;
}

.submenu.visible {
  display:block;
}
<header>
  <div id="item-1" class="menu-item menu-item-1">ITEM 1
    <div id="sub-item-1" class="submenu submenu-1">SUB-ITEM-1</div>
  </div>
  <div id="item-2" class="menu-item menu-item-2">ITEM 2
    <div id="sub-item-2" class="submenu submenu-2">SUB-ITEM-2</div>
  </div>
</header>

最佳答案

有几种不同的方法可以实现这一点,并非所有方法都涉及 JS,我将在下面概述一些可能的方法:
纯 CSS:
第一个(也是最简单的)是仅使用 css。这再次使用 tabindex="-1"喜欢 Samuel's answer使您的菜单项按钮具有焦点。一旦按钮获得焦点,您可以使用 :focus 将一些 CSS 应用到焦点项的关联子菜单。伪类选择器:

.menu-item:focus > .submenu { /* select the focused menu-item's child elements with the class submenu */
  display: block;
}
请参见下面的示例:

body {
  display: flex;
  justify-content: center;
  margin: 0;
  height: 100vh;
  width: 100%;
}

header {
  margin-top: 2rem;
  display: flex;
  width: 50%;
  justify-content: space-evenly;
  align-items: center;
  padding: 1rem;
  background: red;
  height: 2rem;
}

.menu-item {
  position: relative;
  padding: 1rem;
  background: yellow;
  cursor: pointer;
}

.submenu {
  display: none; /* changes to 'block' with CSS */
  padding: 1rem;
  background: lightblue;
  position: absolute;
  top: 4rem;
  left: 0;
  width: 6rem;
}

.menu-item:focus > .submenu {
  display: block;
}
<header>
  <div id="item-1" class="menu-item menu-item-1" tabindex="-1">ITEM 1
    <div id="sub-item-1" class="submenu submenu-1">SUB-ITEM-1</div>
  </div>
  <div id="item-2" class="menu-item menu-item-2" tabindex="-1">ITEM 2
    <div id="sub-item-2" class="submenu submenu-2">SUB-ITEM-2</div>
  </div>
</header>

这样做的主要缺点是我们使用 :focus ,这意味着如果您再次单击菜单项,它将保持焦点而不是模糊,结果将使菜单项保持在 View 中而不是隐藏它。以下使用 JS 的方法可以处理这种情况:
向文档添加事件监听器:
另一种可能的解决方案是更新您的 JS。这包括使用 querySelectorAll() 选择所有菜单项和子菜单项。 .然后,您可以通过浏览 NodeList 将事件监听器添加到您的菜单项。调用 .querySelectorAll() 返回.当您单击一个菜单项时,您可以使用 .querySelector() 获取其关联的子菜单项。当前menuItem .为了在您单击屏幕上的其他位置时隐藏项目,您可以通过向其添加事件监听器来监听文档上的单击事件,并相应地隐藏您的子菜单项。在添加到菜单项的事件监听器中,您可以调用 .stopPropagation() 防止菜单项上的单击事件冒泡到文档并导致文档事件监听器执行(并隐藏所有项目)。

const menuItems = document.querySelectorAll(".menu-item"); // Get all menu items in an array-like structure (NodeList)
const submenuItems = document.querySelectorAll(".submenu"); // select all submenu items

const hideMenus = (menus, ignore) => menus.forEach(menu => { // loop through all items (use: [...menus].forEach((menu) => {) for better browser support)
  if (menu !== ignore) // if we encounter an element that we want to keep visible, skip it, otherwise, remove its visibility
    menu.classList.remove("visible");
});

menuItems.forEach(menuItem => { // loop through the NodeList menu items
  menuItem.addEventListener("click", (e) => {
    e.stopPropagation(); // stop event from bubbling up to the document and executing the below `document.addEventListener()`  when menu item is clicked
    if (e.target === menuItem) { // don't hide when we click on a sub-menu-item (e.target = child sub-menu-item if that is clicked)
      const thisSubmenu = menuItem.querySelector(".submenu");
      thisSubmenu.classList.toggle('visible'); // toggle visibility of submenu under our item
      hideMenus(submenuItems, thisSubmenu); // hide all other submenus
    }
  });
});

document.addEventListener("click", (e) => {
  hideMenus(submenuItems);
});
body {
  display: flex;
  justify-content: center;
  margin: 0;
  height: 100vh;
  width: 100%;
}

header {
  margin-top: 2rem;
  display: flex;
  width: 50%;
  justify-content: space-evenly;
  align-items: center;
  padding: 1rem;
  background: red;
  height: 2rem;
}

.menu-item {
  position: relative;
  padding: 1rem;
  background: yellow;
  cursor: pointer;
}

.submenu {
  display: none;
  /* changes to 'block' with javascript */
  padding: 1rem;
  background: lightblue;
  position: absolute;
  top: 4rem;
  left: 0;
  width: 6rem;
}

.submenu.visible {
  display: block;
}
<header>
  <div id="item-1" class="menu-item menu-item-1">ITEM 1
    <div id="sub-item-1" class="submenu submenu-1">SUB-ITEM-1</div>
  </div>
  <div id="item-2" class="menu-item menu-item-2">ITEM 2
    <div id="sub-item-2" class="submenu submenu-2">SUB-ITEM-2</div>
  </div>
</header>

使用事件委托(delegate):
您可以更新上面的示例以使用 event delegation ,它允许您仅在文档上使用一个事件监听器,而不是为每个菜单项添加一个(从而有助于限制浏览器使用的资源)。然后您可以使用 e.target .closest() 确定您单击了哪个元素(有关详细信息,请参阅代码注释):

const submenuItems = document.querySelectorAll(".submenu"); // select all submenu items

const hideMenus = (menus, ignore) => menus.forEach(menu => { // loop through all items (use: [...menus].forEach((menu) => {) for better browser support)
  if(menu !== ignore) // if we encounter an element that we want to keep visible, skip it, otherwise, remove its visibility
    menu.classList.remove("visible");
});

document.addEventListener("click", (e) => {
  const clickedItem = e.target, menuItem = clickedItem.closest(".menu-item");
  //       v-- use `= menuItem && menuItem.querySelector(...)` for better browser support
  const thisSubmenu = menuItem?.querySelector(".submenu"); // grab the submenu from the menuItem we clicked on (or parent menuItem if we clicked on a submenu item)
  
  if(clickedItem === menuItem) // we clicked on a menu-item
    thisSubmenu.classList.toggle('visible'); // toggle visibility of submenu under our menu-item
    
  hideMenus(submenuItems, thisSubmenu);    
});
body {
  display: flex;
  justify-content: center;
  margin: 0;
  height: 100vh;
  width: 100%;
}

header {
  margin-top: 2rem;
  display: flex;
  width: 50%;
  justify-content: space-evenly;
  align-items: center;
  padding: 1rem;
  background: red;
  height: 2rem;
}

.menu-item {
  position: relative;
  padding: 1rem;
  background: yellow;
  cursor: pointer;
}

.submenu {
  display: none; /* changes to 'block' with javascript */
  padding: 1rem;
  background: lightblue;
  position: absolute;
  top: 4rem;
  left: 0;
  width: 6rem;
}

.submenu.visible {
  display:block;
}
<header>
  <div id="item-1" class="menu-item menu-item-1">ITEM 1
    <div id="sub-item-1" class="submenu submenu-1">SUB-ITEM-1</div>
  </div>
  <div id="item-2" class="menu-item menu-item-2">ITEM 2
    <div id="sub-item-2" class="submenu submenu-2">SUB-ITEM-2</div>
  </div>
</header>

关于javascript - 在元素外部单击时切换元素并移除其可见性 - JavaScript,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/68233038/

相关文章:

javascript - 检测用户何时按下第一个或最后一个表单元素上的选项卡(在 div 中)

javascript - 事件监听器捕获和冒泡阶段事件

javascript - 扩展每个 html 元素

javascript - 如何向 "new"实例添加属性?

php - Laravel 5.1 中发送邮件的事件

c++ - 当光标不在小部件上时,Qt 小部件检测鼠标事件

javascript - 如何检测页面上已访问和未访问的链接?

ios - 如何处理 UIView 中的点击事件

jquery取消选中复选框问题

javascript - 导入的变量未翻译