【问题标题】:Vanilla Javascript Toggle Drop Down MenuVanilla Javascript 切换下拉菜单
【发布时间】:2018-06-09 13:47:16
【问题描述】:

我的大脑已经检查了周末......

我正在寻找一个纯 Javascript 的解决方案,如果在单击另一个主菜单项时打开一个下拉菜单框,则先前打开的下拉菜单将关闭,然后显示新单击的主菜单项的下拉菜单。我知道这可能很简单,但我想不出一个不复杂的解决方案。

此外,如果您在菜单项之外(文档上不是菜单项或下拉框的任何位置)单击,则应关闭所有打开的下拉菜单。

感谢您的帮助。

function testFunc(el) {
  var parent = el.parentElement;
  var dd = parent.lastChild.previousSibling;
  dd.classList.toggle('show');
}
ul { list-style: none; margin: 0; padding: 0; }
ul li {
  width: 100px;
  float: left;
  background: #dbdbdb;
  line-height: 2em;
  text-align: center;
  margin: 0 5px;
  cursor: pointer;
}
ul li span {
  display: block;
}
ul li ul {
  display: none;
}

.show {
  display: block;
}
<ul>
  <li>
    <span onclick="testFunc(this)">Item 1</span>
    <ul>
      <li>Sub Item 1</li>
      <li>Sub Item 2</li>
    </ul>
  </li>
  <li>
    <span onclick="testFunc(this)">Item 2</span>
    <ul>
      <li>Sub Item 1</li>
      <li>Sub Item 2</li>
    </ul>
  </li>
  <li>
    <span onclick="testFunc(this)">Item 3</span>
    <ul>
      <li>Sub Item 1</li>
      <li>Sub Item 2</li>
    </ul>
  </li>
  <li>
    <span onclick="testFunc(this)">Item 4</span>
    <ul>
      <li>Sub Item 1</li>
      <li>Sub Item 2</li>
    </ul>
  </li>
</ul>

【问题讨论】:

    标签: javascript navigation toggle


    【解决方案1】:

    切换菜单可见性

    您可以将上次打开的菜单保存在函数外部的变量opened 中。然后,当单击菜单时,如果 opened 不是 null,它将切换 opened(即隐藏最后打开的菜单)并切换单击的项目。

    let opened = null
    
    function testFunc(el) {
    
      // gets the <ul> element of the clicked menu item
      const menu = el.parentElement.lastChild.previousSibling;
    
      if (!opened) {
    
        // no menu item is shown
        opened = menu
        opened.classList.toggle('show');
    
      } else if (menu == opened) {
    
        // the clicked item is already showing
        menu.classList.toggle('show')
        opened = null
    
      } else {
    
        // the clicked item is hiddden but another one is showing
        opened.classList.toggle('show')
        opened = menu
        opened.classList.toggle('show')
    
      }
    
    }
    

    代码如下:

    let opened = null
    
    function testFunc(el) {
    
      const menu =  el.parentElement.lastChild.previousSibling;
      
      if(!opened) {
        opened = menu
        opened.classList.toggle('show');
      } else if(menu == opened) {
        menu.classList.toggle('show')
        opened = null
      } else {
        opened.classList.toggle('show')
        opened = menu
        opened.classList.toggle('show')
      }
      
    }
    ul {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    
    ul li {
      width: 100px;
      float: left;
      background: #dbdbdb;
      line-height: 2em;
      text-align: center;
      margin: 0 5px;
      cursor: pointer;
    }
    
    ul li span {
      display: block;
    }
    
    ul li ul {
      display: none;
    }
    
    .show {
      display: block;
    }
    <ul>
      <li>
        <span onclick="testFunc(this)">Item 1</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span onclick="testFunc(this)">Item 2</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span onclick="testFunc(this)">Item 3</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span onclick="testFunc(this)">Item 4</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
    </ul>

    ES6 语法的变体

    这是一个带有一些 ES6 语法的变体,注意我已经更改了 HTML 命名结构以更好地维护代码,允许通过类名调用元素

    • 不必使用内联事件侦听器

    • 在一行中调用所有菜单项

    这里是 JavaScript 代码:

    let opened = null
    const toggleVisibility = e => e.classList.toggle('show')
    
    const toggleDropDown = e => {
    
      const clickedItem = e.target.parentElement.lastChild.previousSibling
    
      toggleVisibility(clickedItem);
    
      if (!opened) {
        opened = clickedItem
      } else if (opened == clickedItem) {
        opened = null
      } else {
        toggleVisibility(opened);
        opened = clickedItem
      }
    
    }
    
    [...document.querySelectorAll('.dropDown')].forEach(dropDown => dropDown.addEventListener('click', toggleDropDown))
    

    let opened = null
    const toggleVisibility = e => e.classList.toggle('show')
    
    const toggleDropDown = e => {
    
      const clickedItem = e.target.parentElement.lastChild.previousSibling
    
      toggleVisibility(clickedItem);
    
      if (!opened) {
        opened = clickedItem
      } else if (opened == clickedItem) {
        opened = null
      } else {
        toggleVisibility(opened);
        opened = clickedItem
      }
    
    }
    
    [...document.querySelectorAll('.dropDown')].forEach(dropDown => dropDown.addEventListener('click', toggleDropDown))
    ul {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    
    ul li {
      width: 100px;
      float: left;
      background: #dbdbdb;
      line-height: 2em;
      text-align: center;
      margin: 0 5px;
      cursor: pointer;
    }
    
    ul li span {
      display: block;
    }
    
    ul li ul {
      display: none;
    }
    
    .show {
      display: block;
    }
    <ul>
      <li>
        <span class="dropDown">Item 1</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span class="dropDown">Item 2</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span class="dropDown">Item 3</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span class="dropDown">Item 4</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
    </ul>

    点击其他地方时切换菜单可见性 + 关闭

    如果您想在用户在菜单外单击时关闭任何打开的菜单,您需要在文档本身上有一个事件监听器。因此,不是每个菜单按钮都有一个事件侦听器,而是只有一个事件侦听器来监视文档中发生的任何点击。

    事件监听器将判断点击的项目是否是一个菜单按钮,在这种情况下,它将运行菜单处理程序。否则它将关闭最后打开的菜单项。

    JavaScript 代码:

    let opened = null
    const toggleVisibility = e => e.classList.toggle('show')
    
    const handleDropdown = e => {
    
      const clickedItem = e.parentElement.lastChild.previousSibling
    
      toggleVisibility(clickedItem)
    
      if (!opened) {
        opened = clickedItem
      } else if (opened == clickedItem) {
        opened = null
      } else {
        toggleVisibility(opened)
        opened = clickedItem
      }
    
    }
    
    const handleClick = e => {
    
      if (e.target.className.includes('dropDown')) {
        handleDropdown(e.target)
      } else if (opened) {
        toggleVisibility(opened)
        opened = null
      }
    
    }
    
    document.addEventListener('click', handleClick)
    

    这里是完整的代码:

    let opened = null
    const toggleVisibility = e => e.classList.toggle('show')
    
    const handleDropdown = e => {
    
      const clickedItem = e.parentElement.lastChild.previousSibling
    
      toggleVisibility(clickedItem)
    
      if (!opened) {
        opened = clickedItem
      } else if (opened == clickedItem) {
        opened = null
      } else {
        toggleVisibility(opened)
        opened = clickedItem
      }
    
    }
    
    const handleClick = e => {
    
      if (e.target.className.includes('dropDown')) {
        handleDropdown(e.target)
      } else if (opened) {
        toggleVisibility(opened)
        opened = null
      }
    
    }
    
    document.addEventListener('click', handleClick)
    ul {
      list-style: none;
      margin: 0;
      padding: 0;
    }
    
    ul li {
      width: 100px;
      float: left;
      background: #dbdbdb;
      line-height: 2em;
      text-align: center;
      margin: 0 5px;
      cursor: pointer;
    }
    
    ul li span {
      display: block;
    }
    
    ul li ul {
      display: none;
    }
    
    .show {
      display: block;
    }
    <ul>
      <li>
        <span class="dropDown">Item 1</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span class="dropDown">Item 2</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span class="dropDown">Item 3</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
      <li>
        <span class="dropDown">Item 4</span>
        <ul>
          <li>Sub Item 1</li>
          <li>Sub Item 2</li>
        </ul>
      </li>
    </ul>

    【讨论】:

    • 真棒伊万。但正如 31piy 所提到的,实际的切换功能不再起作用。如果保留父级的切换功能但在顶部添加新功能,那将是理想的。
    • @Ivan 完美!太感谢了!如果在菜单项之外(文档上不是菜单项或下拉框的任何地方)单击关闭任何打开的下拉菜单,有什么想法吗?
    • 我已将第三部分添加到我的答案中。它实际上是一种更有效的处理鼠标点击的方法:有一个事件监听器(与第一个版本相比,每个菜单项添加一个)
    • 伟大的伊万!完美运行……比我最初处理如此看似简单的任务所需的代码少得多。谢谢我的兄弟!
    【解决方案2】:

    很难与 Ivan 的答案竞争,但这将是我使用 ES6 语法对同一问题的解决方案:

    class Dropdown {
    
      constructor() {
        this.listen();
      }
    
      // Listen to ALL (!) click events to also catch clicks OUTSIDE the dropdowns
      listen() {
        document.addEventListener('click', (e) => {
          if (e.target.classList.contains('dropdown')) {
            this.closeOthers(e.target);
            this.handleClick(e.target);
          } else {
            this.closeOthers(null);
          }
        });
      }
    
      // Add or remove 'expanded' CSS class, depending on the current situation
      handleClick(dropdown) {
        if (dropdown.classList.contains('expanded')) {
          dropdown.classList.remove('expanded');
        } else {
          dropdown.classList.add('expanded');
        }
      }
    
      // Close all dropdowns except the one that gets passed as the element parameter
      // Note that we may also pass null in order to close ALL dropdowns
      closeOthers(element) {
        document.querySelectorAll('.dropdown').forEach((dropdown) => {
          if (element != dropdown) {
            dropdown.classList.remove('expanded');
          }
        });
      }
    
    }
    
    document.addEventListener('DOMContentLoaded', () => new Dropdown);
    

    它对我有用。不确定它是否适用于其他人。感谢您的反馈。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-17
      • 2018-02-06
      • 2013-02-23
      • 2020-10-14
      相关资源
      最近更新 更多