我们如何通过外部点击关闭切换菜单

How can we close a toggle menu with an outside click

我是编码新手,我制作了一个外观不错的网站 (https://garibpathshala.in/),带有用于移动设备的切换导航菜单。

有什么方法可以让我们在菜单外单击时关闭菜单。

请看看我的代码并帮助我:)

HTML

      <ul class="header-nav-links">
    <li class="active"><a href="https://garibpathshala.in">HOME</a></li>
    <li><a href="#projects_section">PROJECTS</a></li>
    <li><a href="#meet_the_team_section">TEAM</a></li>
    <li><a href="#about_us_section">ABOUT</a></li>
    <li><a href="https://gallery.garibpathshala.in">GALLERY</a></li>
    <li><a href="https://contact.garibpathshala.in">CONTACT</a></li>
    <li><a href="https://donate.garibpathshala.in">DONATE</a></li>
    <li><a href="https://join.garibpathshala.in">JOIN US</a></li>
   </ul>
  
   <div class="burger">
      <div line1></div>
      <div line2></div>
      <div line3></div>
    </div>

JS

const burger = document.querySelector(".burger");
const navLinks = document.querySelector(".header-nav-links");
const links = document.querySelectorAll(".header-nav-links li");

//Toggle Nav
burger.addEventListener("click", () => {
    navLinks.classList.toggle("open");

//Animate Links
links.forEach((link, index) => {
    if (link.style.animation) {
        link.style.animation = ""
    }else{
        link.style.animation = `navLinkFade 0.5s ease forwards ${index / 7+0.2}s`;
    }
});
});

Here is a screenshot of the nav menu

您可以在 bodydocument 上添加点击侦听器并依赖 event delegation 采取适当的操作,如以下示例代码所示。

(请参阅 in-code 评论以获得进一步说明。)

// Selects some DOM elements and converts the collection to an array
const listItems = Array.from(document.querySelectorAll(".header-nav-links > li"));

// Calls `handleMenuDisplay` when anything is clicked
document.addEventListener("click", handleMenuDisplay);

// Defines `handleMenuDisplay`
function handleMenuDisplay(event){ // Listeners can access their triggering events
  const clickedThing = event.target; // The event's `target` property is useful

  // Depending on what was clicked, takes an appropriate action
  if(listItems.includes(clickedThing)){ // Arrays have an `includes` method
    openMenu(clickedThing);
  }
  else{
    closeMenu();
  }
}

function openMenu(clickedLi){
  demo.textContent = clickedLi.dataset.demoText;
}

function closeMenu(){
  demo.textContent = "Menu is closed";
}
li{ margin: 7px; padding: 3px; border: 1px solid grey; }
#demo{ margin-left: 2ch; font-size: 1.5em; font-weight: bold; }
<ul class="header-nav-links">
  <li data-demo-text="Home menu is open">HOME</li>
  <li data-demo-text="Projects menu is open">PROJECTS</li>
  <li data-demo-text="Team menu is open">TEAM</li>
  <li data-demo-text="About menu is open">ABOUT</li>
</ul>

<p id="demo">Menu is closed</p>

注意:我使用自定义 data-attributes 只是为了让示例代码更简洁一些——它不是事件委托的一部分,每个 li 的显示文本可以很容易地已在脚本中手动写出。

基于其他解决方案:

一种更简单的方法是使用 DOM 元素的焦点和模糊状态来处理菜单的状态。

document.querySelectorAll('.menu').forEach((menu) => {
    const items = menu.querySelector('.menu-items');

    menu.addEventListener('click', (e) => {
        items.classList.remove("hide");
        menu.focus(); // Probably redundant but just in case!
    });

    menu.addEventListener('blur', () => {
        items.classList.add("hide");
    });
});
.menu {
    cursor: pointer;
    display: inline-block;
}

.menu > span {
    user-select: none;
}

.menu:focus {
    outline: none;
    border: none;
}

.hide {
    display: none;
}

.menu-items > * {
    user-select: none;
}
<div class="menu" tabindex="0">
    <span>Menu +</span>
    <div class="menu-items hide">
        <div>Item 0</div>
        <div>Item 1</div>
        <div>Item 2</div>
    </div>
</div>

这里的秘诀是给 .menu div 一个 tabindex 以便它可以被浏览器聚焦。

JS 的首要任务:

  • 在页面中搜索菜单的任何实例 class document.querySelectorAll
  • 获取当前被 .hide
  • 隐藏的项目元素
  • 当有人点击菜单时,从项目中删除 .hide class
    • 这应该聚焦 div,但以防万一 menu.focus 被调用!
  • 然后每当有人失去对 div 的关注时,AKA 点击离开等。添加 .hide class 回来。

这可以扩展为更改按钮的文本,做各种其他事情。它还可以让你的代码超级干净,因为你依赖于浏览器自己的内部状态管理,所以你不必做任何检查。

处理第二次点击关闭

是的,它工作得很好,但是为了让它像大多数人期望的那样运行 UI 我们需要在再次单击菜单 div 时关闭它(替换 span 与任何你需要的 class):

...
    menu.querySelector('span').addEventListener('click', (e) => {
        e.stopPropagation();
        menu.blur();
    });

    menu.addEventListener('click', (e) => {
        items.classList.remove("hide");
        menu.focus(); // Probably redundant but just in case!
    });

    menu.addEventListener('blur', () => {
        items.classList.add("hide");
    });
...

如果 event.CurrentTarget 不是汉堡菜单和文档中的任何其他内容(html 或 body),您可以从菜单中删除“打开”class被点击。

您还需要对 .hamburgernavLinks 本身进行 stopImmediatePropagation 以阻止这些点击被注册为对 body 的点击,因为它们是 [= body 的 33=],否则事件会冒泡到 body。 MDN 参考:https://developer.mozilla.org/en-US/docs/Web/API/Event/bubbles

const burger = document.querySelector(".burger");
const navLinks = document.querySelector(".header-nav-links");
const links = document.querySelectorAll(".header-nav-links li");
const body = document.querySelector('html');

//Toggle Nav
burger.addEventListener("click", (e) => {
    navLinks.classList.toggle("open");
    e.stopImmediatePropagation();

    //Animate Links
    links.forEach((link, index) => {
        if (link.style.animation) {
            link.style.animation = "";
        }else{
            link.style.animation = `navLinkFade 0.5s ease forwards ${index / 7+0.2}s`;
        }
    });
});

navLinks.addEventListener("click", (eve) => {
     eve.stopImmediatePropagation();
});

body.addEventListener("click", (ev) => {

      if (ev.currentTarget != burger) {
          navLinks.classList.remove("open");
      }
});
.burger {
    display: block;
    cursor:pointer;
}

.header-nav-links {
    display: block;
}

.header-nav-links.open {
    transform: translateX(0%);
}

 
.header-nav-links {
    right: 0;
    position: fixed;
    height: 92vh;
    top: 16vh;
    background-color: rgba(0, 0, 0, 0.7);
    display: flex;
    flex-direction: column;
    align-items: center;
    width: 50%;
    transform: translateX(100%);
    transition: transform 0.5s ease-in;
}

.header-nav-links li {
    list-style-type: none;
}

.header-nav-links li:hover {
    border: 1px solid #fff;
    border-radius: 6pc;
    background-color: #007bff;
}

.header-nav-links a {
    color: whitesmoke;
    text-decoration: none;
    font-family: Arial, sans-serif;
    font-weight: normal;
    font-size: 16px;
    border: 0px solid white;
    transition: 400ms;
    padding: 5px 15px;
    border-radius: 19px; 
}
<ul class="header-nav-links">
    <li class="active"><a href="https://garibpathshala.in">HOME</a></li>
    <li><a href="#projects_section">PROJECTS</a></li>
    <li><a href="#meet_the_team_section">TEAM</a></li>
    <li><a href="#about_us_section">ABOUT</a></li>
    <li><a href="https://gallery.garibpathshala.in">GALLERY</a></li>
    <li><a href="https://contact.garibpathshala.in">CONTACT</a></li>
    <li><a href="https://donate.garibpathshala.in">DONATE</a></li>
    <li><a href="https://join.garibpathshala.in">JOIN US</a></li>
</ul>
  
<div class="burger">
  BURGER
  <div line1></div>
  <div line2></div>
  <div line3></div>
</div>