使用html,css和javascript在一个简单的菜单中冒泡

时间:2018-08-25 10:04:12

标签: javascript html css addeventlistener event-bubbling

在下面的简单菜单代码中,如果子菜单已展开/显示,那么我需要在某些情况下折叠子菜单:

  1. 当单击主菜单或子菜单LI时
  2. 当视口大于定义的最小宽度时
  3. 当点击发生在屏幕上的其他任何地方

只要我不为mainMenuID元素声明addEventListener,数字2就可以工作,数字1可以工作。由于某种原因,当我为任何mainMenuID元素添加该事件侦听器时,它将优先于其他任何事件,因此单击除Portfolio或其子菜单项之外的主菜单项,展开和折叠菜单,但是单击submenuID或ulID不执行任何操作。删除mainMenuID的事件监听器后,请单击submenuID或ulID,以展开/折叠子菜单。

第二个问题是,如何向页面的其余部分添加单击事件侦听器,以便在显示子菜单时,单击会折叠子菜单。

谢谢!

<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
</head>

<style media="screen">
body{
  background-color: black;
}

nav ul {
    position: relative;
    text-align: left;
}

nav ul li {
    display: block;  /* WHAT????  REMOVING THIS ACTUALLY MAKES TEXT-DECORATIONS REAPPEAR.... WHY???*/
}

nav a {
    text-decoration: none;
}

/* Hide dropdowns by default */
nav ul ul {
    position: relative;
    display: none;
    left: 0;
}

/* Display Dropdowns on Click */
    nav ul li.showSubmenu > ul {
    display: block;
    position: relative;
    width: 30%;
}

.menu a {
    text-decoration-style: none;
    text-decoration: none;
    background-color: black;
    color: white;
}

.menu a:hover{
    background-color: white;
    color: black;
}
</style>

<body>
    <nav id="mainMenuID" class="menu">
        <ul>
            <li class="menuItem"><a href="#">Home</a></li>
            <li class="menuItem"><a href="#">About</a></li>
            <li id="submenuItem" class="submenu_class"><a href="#"><span id="submenuID">Portfolio ▼</span></a>
            <ul id="ulID">
                <li><a href="#">Landscape</a></li>
                <li><a href="#">Architecture</a></li>
                <li><a href="#">Animal</a></li>
                <li><a href="#">Other</a></li>
            </ul>
            </li>
            <li class="menuItem"><a href="#">Information</a></li>
            <li class="menuItem"><a href="#">Contact</a></li>
       </ul>
    </nav>


<script>
function openSubmenu() {
    if (document.getElementsByClassName('showSubmenu').length == 0){
      document.getElementById("submenuItem").classList.toggle("showSubmenu");
      document.getElementById("submenuID").textContent = "Portfolio ▲";
    } else {
      document.getElementById("submenuItem").classList.toggle("showSubmenu");
      document.getElementById("submenuID").textContent = "Portfolio ▼";
    }
}


function resetSubmenu() {
    var submenuElements = document.getElementsByClassName('submenu_class showSubmenu');
    for (var i = 0; i < submenuElements.length; i++) {
    submenuElements[i].setAttribute('class', 'submenu_class');
    document.getElementById("submenuID").textContent = "Portfolio ►";
    }
}

function screenWidthFunction(x) {
    if (x.matches) {//if it's a narrow screen
        document.getElementById("submenuID").textContent = "Portfolio ▼";
        document.getElementById("ulID").addEventListener("click", openSubmenu, true);
        document.getElementById("submenuID").addEventListener("click", openSubmenu, true);
        document.getElementById("mainMenuID").addEventListener("click", openSubmenu, true);
    } else {
        resetSubmenu();
        document.getElementById("submenuID").textContent = "Portfolio ►";
        document.getElementById("ulID").removeEventListener("click", openSubmenu);
        document.getElementById("submenuID").removeEventListener("click", openSubmenu);
        document.getElementById("mainMenuID").removeEventListener("click", openSubmenu);
      }
   }

    var x = window.matchMedia("(max-width: 480px)");
    screenWidthFunction(x);
    x.addListener(screenWidthFunction);
</script>

<> 嗨,Robidu,我只使用CSS来运行此菜单,但是我被引导使用JS来实现其某些功能,这使大多数CSS无法正常工作。 因此,以冰川的速度,我一直在尝试动态学习JS。信不信由你,从视频,W3Schools等中学到东西。在5到6周的大部分时间内,我一直在编写,重新制作此菜单并将其融合为现实。完全荒谬。 看来您了解并很熟悉JS。我需要花更多的时间来尝试弄清楚如何实施您的所有建议,并且一路走来,我很可能会破坏已经起作用的东西。有什么办法可以请您向我提供他们需要去的代码片段,或者只是更具体的建议,从而减轻您的痛苦。 我摘录了您有疑问的部分文本(由于注释的字符限制,我必须将它们分成多个注释):

  1. “ ...您应该使用CSS隐藏子菜单,因为我认为 是默认设置。” q。是的,这是默认设置。我以为我已经在“导航”中这样做了 ul li.showSubmenu> ul {…}”在我的CSS中。
  2. “…实现在DOMContentReady上触发的初始化阶段 建立支持基础架构。” q。这,我永远都不知道该如何弄清楚自己。
  3. ”…需要一个标志,用于指示菜单是否已打开,以便 处理人员可以做出相应的反应……为您自己解决问题 带有“切换”…手边的菜单状态…” q。我认为“ showSubmenu”类的“切换”是一种“标志”。我将如何以及在何处使用变量代替切换?
  4. “…请您放开自己……重复附加和删除活动 侦听器。”,以及稍后的“如果隐藏了菜单,则菜单不会显示任何内容 事件。” q。您是说要在JS代码中的任何位置将侦听器分配给变量?我现在是否仍需要以相同的方式在相同的地方使用侦听器来引用这些变量?
  5. “…将事件侦听器连接到鼠标,以检查鼠标事件 在此处打开子菜单的项目。” q。您的意思是除了我在id =“ submenuID”上拥有的侦听器之外?您的建议有什么不同?
  6. “…将调整大小事件处理程序附加到执行此操作的窗口 校验...” q。我从W3的示例中裁剪了屏幕宽度代码。使用innerWidth / innerHeight有何不同之处?
  7. “ ...将冒泡阶段设置为true以拦截事件”。 q。如果将一种应用于菜单系统中的项目,将另一种应用于页面系统中的其他元素,则无法找到有关冒泡作品的任何解释。我了解基本知识,但无法弄清楚为什么当使用所有三个侦听器时,在此简单菜单上不起作用。
  8. “单击菜单项时折叠菜单…附加事件 每个菜单项的处理程序(最好的地方是菜单内的链接”。 q。我需要为每个LI分配一个ID吗?

  9. 总体问题:我将NAV元素与UL和LI一起使用是因为 有人告诉我这是屏幕阅读器最容易访问的地方。有没有 更好的方法来执行此菜单中要执行的操作,或者我正在排序 在正确的轨道上?

对于所有后续问题/澄清,我深表歉意,但我真诚地为此打转,盘旋,在汹涌的大海中,戴着眼罩……。以及其他任何可以比喻的类推。

我非常感谢您可以提供任何具体的代码来证明您的建议的细节。 (甚至为我指出示例,以便我尝试找出它们)。

1 个答案:

答案 0 :(得分:2)

在这里应该进行一些重新组装。

首先,您应该使用CSS隐藏子菜单,因为我认为这是默认设置。此外,您应该实现在 DOMContentReady 上触发的初始化阶段,以建立支持基础结构。这样,您可以避免重复连接和删除事件侦听器的麻烦。您可能希望将事件检查器附加到在此处打开子菜单的项目中检查鼠标事件。您还需要一个标志来指示菜单是否处于打开状态,以便处理程序可以做出相应的反应(这样,您就可以避免“切换”之类的问题,并且如果需要,可以立即获得菜单的状态)进行其他检查)。一个简单的变量应该可以解决问题。 有利的一面:如果菜单隐藏,则根本不会发生任何事件。

如果您对窗口大小感兴趣,建议您改用window.innerWidth / window.innerHeight,因为这是一个数值,您可以轻松地将其与您所需的最小尺寸。只需将一个调整大小的事件处理程序附加到执行此检查的窗口,即可设置好。如果窗口大小减小到最小以下,只需强制菜单折叠即可。

只要用户单击文档中的任何位置,就折叠菜单,在此对象上附加一个事件侦听器以查找鼠标单击/键击即可达到目的(将冒泡阶段设置为 true 在其他任何事情发生之前就拦截事件。

单击菜单项时折叠菜单最好通过将事件处理程序附加到每个菜单项来实现(最好的位置是菜单内的链接-将冒泡阶段设置为 false )仅关闭菜单。

编辑:

我现在通过修改HTML和CSS进行了一些修改,这就是我想出的(请注意,我也已将文件转换为XHTML-但这取决于您是否要执行那个):

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xml:lang="en">
<head>
<title>Submenu Test Case</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />

<style type="text/css">
body {
  background-color: black;
  color: white;
  }

aside {
  width: 15em;
  float: left;
  }

nav ul {
  position: relative;
  text-align: left;
  list-style: none;         /* Kills any list decoration */
  }

nav ul ul {
  position: relative;
  left: 0;
  }

nav a {
  text-decoration: none;
  }

/* This ARIA attribute can greatly improve accessibility and can also be used
   to actually hide elements just by tying "display: none;" to it... */
[aria-hidden="true"] {
  display: none;
  }

.menu a {
  color: white;
  }

.menu a:hover, .menu span:hover {
  background-color: white;
  color: black;
  }

/* Style definitions that override settings for mobile devices go here! */
@media screen and (min-width: 480px)
  {
/* Reposition the submenu if it's on a sufficiently wide window... */
  nav ul li > ul {
    position: absolute;
    margin-top: -1.2em;
    left: 7em;
    }
  }
</style>
<script type="application/javascript">
/* <![CDATA[ */
var submenu_open = false;   // Has the submenu been opened?
var need_mobile = false;    // Are we on a narrow window?

// Takes care of hiding and showing the submenu
function ToggleMenu(p_event)
  {
// Do not activate e. g. on a right click...
  if(p_event.button != 0)
    return;

  if(submenu_open)
    {
// If the submenu has previously been open, close it (and adjust the
// controlling menu item if necessary)
    document.getElementById('sub1').setAttribute('aria-hidden', 'true');
    if(window.innerWidth < 480)
      document.getElementById('sub-item1').childNodes[0].childNodes[0].data = 'Portfolio ▼';
    }
  else
    {
// If the submenu has previously been closed, open it (and adjust the
// controlling menu item if necessary)
    document.getElementById('sub1').setAttribute('aria-hidden', 'false');
    if(window.innerWidth < 480)
      document.getElementById('sub-item1').childNodes[0].childNodes[0].data = 'Portfolio ▲';
    }

// This prevents the document's root node (i. e. the document object) from
// seeing the event when clicking on the superordinate item for the submenu...
  p_event.stopPropagation();

  submenu_open = !submenu_open;
  }

// Triggered upon clicking anywhere inside of the document...
function CloseMenu(p_event)
  {
  if(!submenu_open)
    return;

  document.getElementById('sub1').setAttribute('aria-hidden', 'true');
  if(window.innerWidth < 480)
    document.getElementById('sub-item1').childNodes[0].childNodes[0].data = 'Portfolio ▼';
  submenu_open = false;
  }

function CheckWindowSize(p_event)
  {
  if(window.innerWidth < 480)
    {
    if(need_mobile)
      return;

// On a mobile device, insert the submenu into the main one...
    if(submenu_open)
      document.getElementById('sub-item1').childNodes[0].childNodes[0].data = 'Portfolio ▲';
    else
      document.getElementById('sub-item1').childNodes[0].childNodes[0].data = 'Portfolio ▼';
    }
  else
    {
    if(!need_mobile)
      return;

// If the window is wide enough, we can display the submenu next to the main
// one...
    document.getElementById('sub-item1').childNodes[0].childNodes[0].data = 'Portfolio ►';
    }

  need_mobile = !need_mobile;
  }

// Initialization sequence (adds a few event handlers)
document.addEventListener('DOMContentLoaded', function (p_event) {
  document.getElementById('sub-item1').addEventListener('click', ToggleMenu, false);
  window.addEventListener('resize', CheckWindowSize, false);
  document.addEventListener('click', CloseMenu, false);

// If we are on a mobile device, adjust the text of the menu item.  
  if(window.innerWidth < 480)
    {
    need_mobile = true;
    document.getElementById('sub-item1').childNodes[0].childNodes[0].data = 'Portfolio ▼';
    }
  }, false);
/* ]]> */
</script>
</head>
<body>
<header><h1>Submenu Test Case</h1></header>
<aside>
<nav class="menu">
  <ul>
    <li class="menuItem"><a href="javascript:alert('\'Home\' triggered!');">Home</a></li>
    <li class="menuItem"><a href="javascript:alert('\'About\' triggered!');">About</a></li>
    <!-- Assume normal operation here (window width >= 480 pixels) so the
         text is set accordingly...
         Please note that I have removed some extraneous elements and
         attributes. -->
    <li class="submenu" id="sub-item1"><span>Portfolio ►</span>
    <ul id="sub1" aria-hidden="true">
      <li><a href="javascript:alert('\'Landscape\' triggered!');">Landscape</a></li>
      <li><a href="javascript:alert('\'Architecture\' triggered!');">Architecture</a></li>
      <li><a href="javascript:alert('\'Animal\' triggered!');">Animal</a></li>
      <li><a href="javascript:alert('\'Other\' triggered!');">Other</a></li>
    </ul>
    </li>
    <li class="menuItem"><a href="javascript:alert('\'Information\' triggered!');">Information</a></li>
    <li class="menuItem"><a href="javascript:alert('\'Contact\' triggered!');">Contact</a></li>
  </ul>
</nav>
</aside>
<main />
</body>
</html>

请查看(X)HTML中的注释,以了解发生了什么事情的详细信息。 通过对此进行研究,我发现我可以大大简化我再次提到的方法,因此可以归结为以下三个事件:

  • 调整大小:当窗口宽度低于某个阈值时切换菜单布局
  • 鼠标单击菜单项:打开或关闭子菜单
  • 在其他任何地方单击鼠标:关闭菜单

关于您的问题...

广告1 .:我对您提供的CSS进行了一些重新设计。我已经抛出了一些定义,并将所有元素都隐藏在aria-hidden属性中(如果将其设置为true,则不会显示该元素)。所述属性还有助于改善可访问性。在此示例中,如果您无法在屏幕上看到它,那么屏幕阅读器也不会显示它。

ad 2 .:设置非常简单。只需添加document.addEventListener('DOMContentLoaded', function (p_event) { }, false); 在主执行路径中,然后将需要设置的所有内容添加到函数中。这对于所谓的非侵入式JavaScript(即,其本身在需要时将钩子附加到文档而不是将其硬编码到(X)HTML中)的JavaScript尤其重要。

ad 3 .:您可以使用简单的变量(指示菜单是否打开的布尔值)来完成此操作。然后,您可以快速检查菜单的状态,而不必查询DOM。

ad 4 .:充其量是非常繁琐和昂贵的,因此处理程序仅被连接一次。其余的应该由控制逻辑决定是否忽略任何事件(如果不满足某些条件,只需返回即可)。

ad 5 .:它采用了您已经实现的那种处理程序,但是我做了一点简化,以避免在不必要的情况下调用DOM(它使用上述标志),而且还考虑了窗口的调整文字的宽度。

广告6:innerWidth / innerHeight是可以由DOM轻松返回的数字值。我不知道您的方法,但据我估计,这似乎有点昂贵,而且当您将值存储在变量中时,您可以对其进行多次检查(例如,如果需要检查不同的宽度/高度) ),只需进行简单比较即可。您的方法将要求您重置匹配条件。

广告7 .:我需要在这里纠正自己,因为通过仔细考虑您的问题,我发现所有内容(即addEventListener的第三个参数)都应设置为 false 。否则,执行顺序会混乱,或者某些链接首先看不到该事件。

广告8 .:事实证明也没有必要。我最初的答案来自我用JavaScript实现的上下文菜单,但是由于其本质,我不得不借助这些处理程序将其关闭。这里的情况有些不同,因此只需省略这些处理程序即可简化事情。

广告9 .:您实际上已经在这里选择了最佳方法。实施导航时,我也在使用这种方法。

我希望我可以将您的一些问号变成感叹号。但是,如果仍然有疑问,请询问。没有比未解决的问题更糟糕的了。