我有一个菜单,在单击标题时打开一个子导航,我试图通过单击页面上除打开元素以外的任何位置来关闭该标题。
我的代码片段如下:
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 树上冒泡(。无论如何,您都可以关闭所有article
,然后在适当的情况下显示单击的条目(及其祖先(。
下面的代码,但短了很多好处:
- 它是动态的。这意味着它可以处理任意数量的子级别。
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>