通过使用纯Javascript单击页面上的任意位置来关闭元素

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通过使用纯Javascript单击页面上的任意位置来关闭元素相关的知识,希望对你有一定的参考价值。

我有一个菜单打开一个子导航点击一个标题,我试图通过点击页面上的任何一个除了一个打开的元素关闭。

我的代码片段如下:

function showSubMenu(show, hide1, hide2, hide3, hide4) {
  document.getElementById(show).className = "subNavShow";
  document.getElementById(hide1).className = "subNavHide";
  document.getElementById(hide2).className = "subNavHide";
  document.getElementById(hide3).className = "subNavHide";
  document.getElementById(hide4).className = "subNavHide";
}
.subNavHide {
  display: none;
}

.subNavShow {
  display: block;
}
<ul class="topnavList" id="siteTopnavList">
  <li>
    <a onclick="showSubMenu('text1','text2','text3','text4','text5')" href="javascript:void(0);">Nav 1</a>
    <article id="text1" class="subNavHide">
      <ul>
        <li><a href="#">Sub Nav 1</a></li>
      </ul>
    </article>
  </li>
  <li>
    <a onclick="showSubMenu('text2','text1','text3','text4','text5')" href="javascript:void(0);">Nav 2</a>
    <article id="text2" class="subNavHide"> text2 </article>
  </li>
  <li>
    <a onclick="showSubMenu('text3','text1','text2','text4','text5')" href="javascript:void(0);">Nav 3</a>
    <article id="text3" class="subNavHide"> text3 </article>
  </li>
  <li>
    <a onclick="showSubMenu('text4','text1','text2','text3','text5')" href="javascript:void(0);">Nav 4</a>
    <article id="text4" class="subNavHide"> text4 </article>
  </li>
  <li>
    <a onclick="showSubMenu('text5','text1','text2','text3','text4')" href="javascript:void(0);">Nav 5</a>
    <article id="text5" class="subNavHide"> text5 </article>
  </li>
</ul>

理想情况下,我想使用纯Javascript,但如果Jquery绝对必要,那么我也可以

答案

在我看来,使用当前实现执行此操作的最简单方法是向文档添​​加单击事件侦听器,并使用.closest确定单击的元素是否为打开的元素:

document.addEventListener(`click`, hideSubMenus);

function hideSubMenus(event) {
    if (!event.target.closest(`.topnavList li a, .subNavShow`)) {
        document.getElementById(`text1`).className = `subNavHide`;
        document.getElementById(`text2`).className = `subNavHide`;
        document.getElementById(`text3`).className = `subNavHide`;
        document.getElementById(`text4`).className = `subNavHide`;
        document.getElementById(`text5`).className = `subNavHide`;
    }
}

然而,closest与旧版浏览器不兼容:https://developer.mozilla.org/en-US/docs/Web/API/Element/closest

但我可能会在链接中添加类并向其添加事件侦听器,而不是使用“onclick”属性。这样,例如,如果您将“subNavLink”类添加到每个链接,您可以使用循环来处理链接,而不是为每个链接重复相同的行:

let links, i, n;
links = document.getElementsByClassName(`subNavLink`);
for (i = 0, n = links.length; i < n; i++) {
    links[i].addEventListener(`click`, showSubMenu);
}

function showSubMenu(event) {
    let currentLink, i, link, n;
    currentLink = event.currentTarget;
    for (i = 0, n = links.length; i < n; i++) {
        link = links[i];
        if (link === currentLink) {
            // this link was clicked, so we have to show its submenu
            link.nextElementSibling.className = `subNavShow`;
        } else {
            // this link was not clicked, so we have to hide its submenu
            link.nextElementSibling.className = `subNavHide`;
        }
    }
}

通过这样做,您可以将hideSubMenus函数更改为:

function hideSubMenus(event) {
    let i, n;
    if (!event.target.closest(`.subNavLink, .subNavShow`)) {
        for (i = 0, n = links.length; i < n; i++) {
            links[i].nextElementSibling.className = `subNavHide`;
        }
    }
}
另一答案

我发现最简单的方法就是在菜单下面创建一个图层(或者更常见的是模态窗口)。然后使用该图层作为元素来测试它是否已被点击(相对于坐在它上面的元素)。

(该示例使用灰色背景来显示叠加层的存在,但它可以很容易地成为透明DIV并且仍然具有相同的效果)

// Get the elements that will show/hide
const overlay = document.getElementById('overlay');
const menu = document.getElementById('menu');

// Change the className to have the CSS that will hide
// the elements
// Since the 'menu' element is on top of the 'overlay'
// element, clicking on the 'menu' should not click
// through the 'overlay' -- thus ignoring this section
// of code to hide things
overlay.onclick = function(){
  menu.className = 'hide';
  overlay.className = 'hide';
};

// Quick and dirty code to reset the page and display 
// the 'menu' and 'overlay' DIVs
function open(){
  menu.className = '';
  overlay.className = '';
}
#overlay{
  display: block;
  position: fixed;
  top: 0; left: 0;
  height: 100%; height: 100vh;
  width: 100%; width: 100vw;
  background-color: rgba( 0, 0, 0, 0.25 );
}
#overlay.hide{ display: none; }

#menu{
  position: absolute;
  background-color: white;
  padding: 15px; border-radius: 5px;
}
  #menu ul, #menu li{
    margin: 0; padding: 0;
    list-style: none;
  }
#menu.hide{ display: none; }
<a href="javascript:open();">OPEN</a>

<div id="overlay"></div>

<div id="menu">
  <ul>
    <li>Menu Item</li>
    <li>Menu Item</li>
    <li>Menu Item</li>
    <li>Menu Item</li>
  </ul>
</div>
另一答案

代码越通用越好。

使用文档上设置的eventListener,您可以在页面上收听所有“点击”事件(在DOM树中冒泡)。您可以关闭所有articles,无论如何,然后在适当的时候显示点击的条目(及其祖先)。

下面的代码,但有很多好处:

  • 它是动态的。这意味着它可以处理任何数量的子级别。 article元素既不需要id属性也不需要在第一次渲染时显示/隐藏类。代码变得松散耦合。
  • 只有一个处理函数将存储在内存中,而不是每个菜单项一个。
  • 它将处理稍后(在eventListener注册后)添加到菜单中的条目。
  • 您的代码是分解的,这使得它更易于阅读和重用。

let topNavList = document.querySelector('#siteTopnavList');

document.addEventListener('click', function (e) {
    let t = e.target;
    
    // At this point, close menu entries anyway
    topNavList.querySelectorAll('a ~ article').forEach(el => {
       el.classList.add('subNavHide'); el.classList.remove('subNavShow');
    });
    
    // Drop clicks on the "active" link or any element that is outside the `#siteTopnavList` menu
    if (!t.nextElementSibling || t.nextElementSibling.classList.contains('subNavShow')) {
      return;
    }
    
    if (t.nodeName.toLowerCase() == 'a' && topNavList.contains(t)) {
       topNavList.querySelectorAll('article').forEach(x => {
           if(x.contains(t) || x === t.nextElementSibling) {
            x.classList.remove('subNavHide');
            x.classList.add('subNavShow');
           }
       });

       // Prevent the browser to process the anchor href attribute
       e.preventDefault();
    }
});
#siteTopnavList article {display:none}
#siteTopnavList .subNavShow {display:block}
<ul class="topnavList" id="siteTopnavList">
    <li>
        <a href="#">Nav 1</a>
        <article> 
            <ul>
                <li><a href="#">Sub Nav 1</a></li> 
            </ul>
        </article>
    </li>
    <li>
        <a href="#">Nav 2</a>
        <article> TEXT2 </article>
    </li>
    <li>
        <a href="#">Multi level</a>
        <article>
            <ul>
                <li>
                  <a href="#">Sub Nav 1</a>
                  <article>
                      <ul>
                          <li><a href="http://nowhere.com">Deep 1</a></li>
                          <li><a href="http://nowhere.com">Deep 2</a></li>
                          <li>
                              <a href="#">Even deeper 3</a>
                              <article> 
                                  <ul>
                                      <li><a href="#">Even deeper 1</a></li> 
                                  </ul>
                              </article>
                          </li>
                      </ul>
                  </article>
                </li> 
            </ul>
        </article>
    </li>
</ul>

以上是关于通过使用纯Javascript单击页面上的任意位置来关闭元素的主要内容,如果未能解决你的问题,请参考以下文章

JTree 通过单击行上的任意位置选择节点

通过Javascript自动单击具有唯一标签和类名的页面上的按钮的方法

如何记录文本在页面上的位置?

单击屏幕上的任意位置关闭所有 Angular JS Bootstrap 弹出窗口?

单击触摸设备 (iPad/iPhone) 上的任意位置以隐藏由 .hover() 切换的下拉菜单

使用纯 Javascript 滚动到可滚动 DIV 内的元素