简体   繁体   English

如何使用 Javascript 通过单击向嵌套在 UL 中的多个子元素添加和删除类?

[英]How do I add and remove classes to multiple child elements nested in an UL with a single click using Javascript?

I want to start by apologizing for such a long question, I just hope I wont make it difficult to understand as a result.我想首先为这么长的问题道歉,我只是希望我不会因此而难以理解。

I have created a side bar with three Menu elements in an UL which expand to show child elements, change background color and remove hover effect when clicked.我在 UL 中创建了一个带有三个菜单元素的侧栏,它们展开以显示子元素、更改背景颜色并在单击时删除 hover 效果。 I did this by defining a function that adds and remove classes containing relevant properties when the menu element is clicked.我通过定义一个 function 来做到这一点,它在单击菜单元素时添加和删除包含相关属性的类。

The four specific things that I want the sidebar to do but cant seem to get it to do are as follows;我希望侧边栏执行但似乎无法执行的四件事如下;

  1. only one selected/clicked item to expand at a time while all the rest of the unselected menu elements are collapsed.一次只展开一个选择/单击的项目,而未选择的菜单元素的所有 rest 都被折叠。 That means if I click the first item, it expands and when I click the second one, the first one collapses while the one I clicked expands etc.这意味着如果我单击第一个项目,它会展开,当我单击第二个项目时,第一个项目会折叠,而我单击的项目会展开等等。
  2. The selected/clicked element changes its background color to indicate it's selected.被选中/点击的元素会改变其背景颜色以表明它被选中。
  3. The selected/clicked element has no hover effect on the text while the unselected elements have a hover effect of text color change on them.选中/点击的元素对文本没有 hover 效果,而未选中的元素对它们有文本颜色变化的 hover 效果。
  4. I also want the selected menu element be able to toggle the expansion on and off not affecting the other elements in the UL.我还希望选定的菜单元素能够打开和关闭扩展而不影响 UL 中的其他元素。

I think where I'm having most trouble with my code is in the adding and removal of classes especially given that the <a> Tags which are nested inside the <li> Tags (clicked elements) are where the "hover" class needs to be added/removed, as well as the <ul> Tags that expand are also nested inside the clicked elements.我认为我的代码最麻烦的地方是添加和删除类,特别是考虑到嵌套在<li>标签内的<a>标签(点击的元素)是“悬停” class 需要的地方被添加/删除,以及展开的<ul>标签也嵌套在点击的元素内。

 function toggleMenu(e) { var kids = document.querySelector("#menuList").children; var unselectedLink = document.querySelectorAll(".unselected a"); var unselectedDropdown = document.querySelectorAll(".unselected ul"); //adds "unselected" class to all elements exept the selected one for (var i = 0; i < kids.length; i++) { kids[i].className = "unselected"; } //adds "menuHover" class to all elements exept the selected element for (var i = 0; i < unselectedLink.length; i++) { unselectedLink[i].className = "menuHover"; } for (var i = 0; i < unselectedDropdown.length; i++) { unselectedDropdown[i].classList.remove("show") } //adds "selected" class, removes "menuHover" class and adds "toggle" to the selected element e.className = "selected"; document.querySelector(".selected a").classList.remove("menuHover"); document.querySelector(".selected ul").classList.toggle("show"); }
 .sidebar { position: fixed; width: 250px; height: 100%; left: 0px; top: 0; background: #1b1b1b; font-family: sans-serif; }.menu-bar { background: #1b1b1b; height: 60px; display: flex; align-items: center; padding-left: 42px; }.side-text { color: #C5C5C5; font-weight: bold; font-size: 20px; } nav ul { background: #1b1b1b; height: 100%; width: 100%; list-style: none; margin-left: 0; padding-left: 0; } nav ul li { line-height: 40px; } nav ul li a { position: relative; color: #C5C5C5; text-decoration: none; font-size: 14px; padding-left: 43px; font-weight: normal; display: block; width: 100%; } nav ul ul { position: static; display: none; } nav ul ul li a { font-family: sans-serif; font-size: 13px; color: #e6e6e6; padding-left: 80px; font-weight: lighter; }.submenu-item:hover { background: #1e1e1e;important. } /*...........selected and show..................*/:selected { background-color; #255DAA. }:show { display; block. } /*...........unselected and hover..................*/:unselected { color; #1e1e1e. }:menuHover:hover { color; #255DAA; }
 <nav class="sidebar"> <div class="menu-bar"> <label class="side-text">MENU</label> </div> <ul id="menuList"> <li class="selected" onclick="toggleMenu(this)"> <a href="#" class="" id="staff-btn">Staff</a> <ul> <li><a href="#">New Staff</a></li> <li><a href="#">View Staff</a></li> </ul> </li> <li class="unselected" onclick="toggleMenu(this)"> <a href="#" id="notes-btn" class="menuHover">Notes</a> <ul> <li><a href="#">New Note</a></li> <li><a href="#">Edit Notes</a></li> </ul> </li> <li class="unselected" onclick="toggleMenu(this)"> <a href="#" class="menuHover" id="tasks-btn">Tasks</a> <ul> <li><a href="#">New Tasks</a></li> <li><a href="#">Edit Task</a></li> </ul> </li> </nav>

I am quite close but somehow, logically I am doing things the wrong way with the JavaScript, so any adjustments to the code to make it reach all four of the above goals will be much appreciated.我非常接近,但不知何故,从逻辑上讲,我在 JavaScript 上做错了事情,因此对代码进行任何调整以使其达到上述所有四个目标都将不胜感激。 thanks谢谢

A simple way to do this:一个简单的方法来做到这一点:

If the element was selected, just unselect it如果元素被选中,只需取消选中它

If it wasnt, unselect all elements and select the clicked element如果不是,请取消选择所有元素和 select 被点击的元素

function toggleMenu(el) {
  if (el.classList.contains("selected")) {
    el.classList.remove("selected");
    el.classList.add("unselected");
  }
  else {
    for (const child of document.getElementById("menuList").children) {
      child.classList.remove("selected");
      child.classList.add("unselected");
    }
    el.classList.remove("unselected");
    el.classList.add("selected");
  }
}

Edit 1 You can use the following css to unhide the submenu of a selected menuitem:编辑 1您可以使用以下 css 取消隐藏所选菜单项的子菜单:

.selected ul {
  display: block;
}

Edit 2 I went to the trouble of actually implementing it.编辑 2我遇到了实际实施它的麻烦。

 function toggleMenu(el) { if (el.classList.contains("selected")) { el.classList.remove("selected"); el.classList.add("unselected"); } else { for (const child of document.getElementById("menuList").children) { child.classList.remove("selected"); child.classList.add("unselected"); } el.classList.remove("unselected"); el.classList.add("selected"); } }
 .sidebar { position: fixed; width: 250px; height: 100%; left: 0px; top: 0; background: #1b1b1b; font-family: sans-serif; }.menu-bar { background: #1b1b1b; height: 60px; display: flex; align-items: center; padding-left: 42px; }.side-text { color: #C5C5C5; font-weight: bold; font-size: 20px; } /* menu */.menu { background: #1b1b1b; height: 100%; width: 100%; list-style: none; margin-left: 0; padding-left: 0; }.menu-item { line-height: 40px; }.menu-item a { position: relative; color: #C5C5C5; text-decoration: none; font-size: 14px; padding-left: 43px; font-weight: normal; display: block; width: 100%; } /* submenu */.submenu { position: static; display: none; list-style: none; }.submenu-item a { font-family: sans-serif; font-size: 13px; color: #e6e6e6; padding-left: 80px; font-weight: lighter; }.submenu-item:hover { background: #1e1e1e; } /* selected and unselected */.selected { background-color: #255DAA; }.selected.submenu { display: block; }.unselected { color: #1e1e1e; }.unselected:hover a { color: #255DAA; }
 <nav class="sidebar"> <div class="menu-bar"> <label class="side-text">MENU</label> </div> <ul class="menu" id="menuList"> <li class="menu-item selected" onclick="toggleMenu(this)"> <a href="#" id="staff-btn">Staff</a> <ul class="submenu"> <li class="submenu-item"><a href="#">New Staff</a></li> <li class="submenu-item"><a href="#">View Staff</a></li> </ul> </li> <li class="menu-item unselected" onclick="toggleMenu(this)"> <a href="#" id="notes-btn">Notes</a> <ul class="submenu"> <li class="submenu-item"><a href="#">New Note</a></li> <li class="submenu-item"><a href="#">Edit Notes</a></li> </ul> </li> <li class="menu-item unselected" onclick="toggleMenu(this)"> <a href="#" id="tasks-btn">Tasks</a> <ul class="submenu"> <li class="submenu-item"><a href="#">New Tasks</a></li> <li class="submenu-item"><a href="#">Edit Task</a></li> </ul> </li> </ul> </nav>

As alternative to the accepted answer, here two different approaches I was working on...作为公认答案的替代方案,我正在研究两种不同的方法......

#menuList li is a container for a list of <a> menu items which have adjacent <ul> sub-menu-items. #menuList li<a>菜单项列表的容器,这些菜单项具有相邻的<ul>子菜单项。 For easy selection with CSS I assigned class .menu-item to those <a> .为了使用 CSS 轻松选择,我将 class .menu-item分配给那些<a>

The CSS logic for both versions is essentially equal:两个版本的CSS 逻辑基本相同:

  • set the .menu-item adjacent ul sub-menu-items hidden by default设置.menu-item相邻的ul子菜单项默认隐藏
  • define :hover colors定义:hover colors
  • define colors for a selected .menu-item and adjacent ul (either :focus or .selected is true)为选定的.menu-item和相邻的ul定义 colors ( :focus.selected为真)
  • make .menu-item adjacent ul sub-menu-items visible when a .menu-item gets selected (ditto).menu-item被选中时,使.menu-item相邻的ul子菜单项可见(同上)

Difference: for CSS only we use the :focus selector, for CSS with Javascript we use class .selected .区别:对于 CSS,我们只使用:focus选择器,对于 CSS 和 Javascript,我们使用.selected

CSS only (automatic focus and blur )仅限 CSS (自动对焦模糊

An <a> gets focus when clicked (like button, input, etc. :focus is true ).单击时<a>获得焦点(如按钮、输入等:focustrue )。 When the user clicks/taps outside the focussed element it automatically loses focus again (gets blurred and :focus is false , as in :not(:focus) = 'blur' ).当用户在焦点元素之外单击/点击时,它会自动再次失去焦点(变得模糊并且:focusfalse ,如:not(:focus) = 'blur' )。 We can use the CSS :focus selector to handle user clicks and modify elements, MDN: ':focus' .我们可以使用 CSS :focus选择器来处理用户点击和修改元素, MDN: ':focus'

CSS with Javascript ( focus and blur on request) CSS 和 Javascript (根据要求进行聚焦模糊

The OP wants a selected .menu-item and its adjacent ul sub-menu-items to stay visible until the user specifically deselects it again. OP 希望选定的.menu-item及其相邻的ul子菜单项保持可见,直到用户再次明确取消选择它。 This cannot be done with the :focus selector, so we ignore that selector and use class .selected instead to handle focus and blur requirements ourselves, MDN: HTMLElement.blur() .这不能使用:focus选择器完成,因此我们忽略该选择器并使用 class .selected代替自己处理焦点模糊要求, MDN:HTMLElement.blur()

The Javascript logic is fairly straightforward: Javascript 逻辑相当简单:

Attach a 'click'- eventListener ( MDN: Element: click event ) to main container #menuList handling:将“click”- eventListener ( MDN: Element: click event ) 附加到主容器#menuList处理:

  • when a .menu-item gets selected and it is the currently .selected then blur it ( menuItemBlur() )当一个.menu-item被选中并且它是当前的.selected然后模糊它( menuItemBlur()
  • otherwise否则
    • when we have a previously selected .menu-item open, blur that first ( menuItemBlur() )当我们打开之前选择.menu-item时,首先模糊它( menuItemBlur()
    • and then focus the newly selected .menu-item ( menuItemFocus() )然后聚焦新选择.menu-item ( menuItemFocus() )

Changes to OP code操作代码的更改

  • removed unneeded CSS删除了不需要的 CSS
  • removed unneeded class attributes from HTML从 HTML 中删除了不需要class属性
  • changed href="#" in <#menuList li a> to href="javascript:void(0)" to prevent it from creating an entry in the browser history (sub-menu-items will still create an entry).<#menuList li a>中的href="#"更改为href="javascript:void(0)"以防止它在浏览器历史记录中创建条目(子菜单项仍将创建条目)。

The below snippet is heavily commented and should be self-explanatory.下面的代码片段被大量评论,应该是不言自明的。

 'use-strict'; var activeItem; // Holds the currently '.selected''.submenu' (null/undefined if none) // Attach 'click' event listener to the #menuList document.getElementById('menuList').addEventListener('click', function(e) { menuItemToggle(e.target) }); function menuItemToggle(el) { if (el.classList.contains('menu-item2')) { // When a '.menu-item' gets clicked (not its kids) if (el.classList.contains('selected')) { // and it is the '.selected''.menu-item' menuItemBlur(el); // then close it and remove focus() } else { if (activeItem) // When there is a currently selected '.menu-item' menuItemBlur(activeItem); // then deactivate it menuItemFocus(el); // Now activate the clicked `.menu-item` }; }; function menuItemBlur(el) { el.classList.remove("selected"); // Set the '.menu-item' to not '.selected' activeItem = null; // and remove the reference to it el.blur(); // Remove focus from element for CSS ':focus' //...extend with other 'Blur' stuff... }; function menuItemFocus(el) { el.classList.add("selected"); // Set the '.menu-item' to '.selected' activeItem = el; // and save a reference to it //...extend with other 'Focus' stuff... }; };
 .sidebar { position: fixed; width: 250px; height: 100%; left: 0px; top: 0; background: #1b1b1b; font-family: sans-serif; }.menu-bar { background: #1b1b1b; height: 60px; display: flex; align-items: center; padding-left: 42px; }.side-text { color: #c5c5c5; font-weight: bold; font-size: 20px; } nav ul { background: #1b1b1b; height: 100%; width: 100%; list-style: none; margin-left: 0; padding-left: 0; } nav ul li { line-height: 40px; } nav ul li a { position: relative; color: #c5c5c5; text-decoration: none; font-size: 14px; padding-left: 43px; font-weight: normal; display: block; width: 100%; } nav ul ul { position: static; display: none; } nav ul ul li a { font-family: sans-serif; font-size: 13px; color: #e6e6e6; padding-left: 80px; font-weight: lighter; } /*************/ /* ADDED CSS */ /*************/ /* All classes starting with "menu-item" */ [class^="menu-item"] + ul { display: none } /* hide adjacent UL */ [class^="menu-item"]:hover { color: #255daa } /* hover color */ a + ul li a:hover { color: #c5c5c5; background-color: #1b1b1b } /* menu-item adjacent sub-menu-items hover colors Here the generic form is used, but it would probably be more clear to be specific and use: - either.menu-item1:focus + ul li a:hover - or.menu-item2.selected + ul li a:hover */ /* ':focus' version This version uses the CSS ':focus' without any Javascript. Main difference with the '.selected' version below is that when the user clicks outside the '.menu-item', the '.menu-item' looses focus and therefore gets hidden again (as:focus is no longer true). */.menu-item1:focus, .menu-item1:focus + ul { color: #e6e6e6; background-color: #255DAA } /* focus colors */.menu-item1:focus + ul { display: block } /* show adjacent UL */ /* '.selected' version, with Javascript. Basically the same CSS, but now using class '.selected' instead of ':focus'. Closing occurs only on user specific 'click'. */.menu-item2.selected, .menu-item2.selected + ul { color: #e6e6e6; background-color: #255DAA } /* focus colors */.menu-item2.selected + ul { display: block } /* show adjacent UL */ /*********************/ /* for demo use only */ /*********************/ nav h3 { color: rgba(100, 149, 237,.9); /* CornflowerBlue */ font-style: italic; padding-left: 43px; }.anchor { color: white; padding-left: 43px; }.content { font-size: 1.5rem; margin: 5rem 300px; } /* preferred globals */ html,body { box-sizing: border-box; width: 100%; max-width: 100% } *::before,*::after, * { box-sizing: inherit } body { margin: 0 }
 <nav class="sidebar"> <div class="menu-bar"> <label class="side-text">MENU</label> </div> <h3>test</h3> <a class="anchor" href="javascript:void(0)">some '.sidebar' &lt;a&gt;</a> <ul id="menuList"> <h3>:focus version</h3> <li> <a class="menu-item1" href="javascript:void(0)">Staff</a> <ul> <li><a href="#">New Staff</a></li> <li><a href="#">View Staff</a></li> </ul> </li> <li> <a class="menu-item1" href="javascript:void(0)">Notes</a> <ul> <li><a href="#">New Note</a></li> <li><a href="#">Edit Notes</a></li> </ul> </li> <li> <a class="menu-item1" href="javascript:void(0)">Tasks</a> <ul> <li><a href="#">New Tasks</a></li> <li><a href="#">Edit Task</a></li> </ul> </li> <h3>.selected version</h3> <li> <a class="menu-item2" href="javascript:void(0)">Staff</a> <ul> <li><a href="#">New Staff</a></li> <li><a href="#">View Staff</a></li> </ul> </li> <li> <a class="menu-item2" href="javascript:void(0)">Notes</a> <ul> <li><a href="#">New Note</a></li> <li><a href="#">Edit Notes</a></li> </ul> </li> <li> <a class="menu-item2" href="javascript:void(0)">Tasks</a> <ul> <li><a href="#">New Tasks</a></li> <li><a href="#">Edit Task</a></li> </ul> </li> </ul> </nav> <div class="content"> <h3><b>Note</b></h3> <p> This demo uses two different approaches interchangeably creating a quirky behaviour, which under normal circumstances would not exist. </p> <p>To reproduce:</p> <ul> <li>select a <i>':focus version'</i> menu item first <li>then select a <i>'.selected version'</i> menu item </ul> <p> As you can see, the selected <i>':focus version'</i> loses focus and a second 'click' is needed to activate the <i>'.selected version'</i> menu item. This is because the first click event of the <i>'.selected version'</i> gets consumed by the blur event of the <i>':focus version'</i>. </p> <p>Just so you know...</p> </div>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM