У меня есть меню, которое открывает подменю по щелчку, добавляя класс active к соответствующему элементу. Однако всякий раз, когда открывалось первое подменю, оно оставалось активным, если открывалось второе подменю и так далее.

Поэтому я добавил forEach, чтобы сначала удалить все классы active и только после этого добавить класс active в следующее подменю.

К сожалению, это вызывает проблему, я не могу понять: Мне нужно, чтобы активное меню закрывалось, если ссылка будет нажата снова .

Следующий код не работает в этом случае, потому что при нажатии на ту же ссылку он сначала удалит все классы active, а затем добавит его снова, в результате чего меню останется открытым, а не закрывается.

const megamenu = document.querySelector('.megamenu');
const menuSection = megamenu.querySelector('.megamenu-section');  
const submenus = document.querySelectorAll('.megamenu-submenu');

menuSection.addEventListener('click', (e) => {
    e.preventDefault();
    submenus.forEach(submenu => {
        if (submenu.classList.contains('active')) {
            submenu.classList.remove('active');
            console.log("hasActive")
    })
    e.target.closest('.megamenu-submenu').classList.toggle('active');
});

Думаю, мне нужен способ удалить все классы active, кроме самого активного подменю.

Любой способ добиться этого? Или какое-нибудь лучшее решение? Спасибо.

0
hardy123480 12 Июн 2021 в 16:11

3 ответа

Лучший ответ

Множественные подходы предназначены для демонстрации того, что всегда есть несколько способов решить одну проблему в программировании.
Некоторые из них могут быть более эффективными, другие - более читабельными и т. Д.

Вы можете выбрать любой подход, который вам больше нравится.

Подход 1 (рекомендованный мной)

Сохранить ссылку на последнее активное подменю как lastActive.
onclick, удалите .active из lastActive и переключите .active в подменю, на которое вы нажали.

Чтобы исправить случай, когда lastActive - это подменю, по которому щелкнули мышью, мы переключаемся в зависимости от того, присутствовал ли .active перед удалением на lastActive.

Для этого не требуется цикла и почти ветвления (if-выражения) с нашей стороны (но, скорее всего, некоторые из них используются в машинном коде (могут использовать некоторые в любом случае)).
Но я думаю, что это создает единое закрытие (без проблем) .

Чтобы lastActive не был виден в глобальном контексте или остальной части скрипта, мы инкапсулируем его в IIFE.

const megamenu = document.querySelector('.megamenu');
const menuSection = megamenu.querySelector('.megamenu-section');  
const submenus = document.querySelectorAll('.megamenu-submenu');


(function() {
  let lastActive = submenus[0];
  menuSection.addEventListener('click', evt => {
    const currentSubmenu = evt.target.closest('.megamenu-submenu');
    if (!currentSubmenu) return;
    
    const wasActive = currentSubmenu.classList.contains('active');
    
    lastActive.classList.remove('active');
    currentSubmenu.classList.toggle('active', !wasActive);
    
    lastActive = currentSubmenu;
  });
})();
.megamenu-submenu {
  border: 1px solid black;
  height: 1.6rem;
  box-sizing: border-box;
}
.megamenu-submenu.active {
  background-color: rgba(255, 0, 0, .3);
}
<div class="megamenu">
  <div class="megamenu-section">
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
  </div>
</div>

Подход 2

Прокрутите каждое активное подменю и удалите .active, кроме того, на которое в данный момент щелкнули. Переключить .active нажатой кнопки.

Чтобы прокручивать только текущие активные подменю, нам нужно запросить внутри слушателя. Это может снизить производительность (минимально) для слишком большого количества элементов.

const megamenu = document.querySelector('.megamenu');
const menuSection = megamenu.querySelector('.megamenu-section');  
const submenus = document.querySelectorAll('.megamenu-submenu');

menuSection.addEventListener('click', evt => {
  const currentSubmenu = evt.target.closest('.megamenu-submenu');
  if (!currentSubmenu) return;
  
  for (const submenu of document.querySelectorAll('.megamenu-submenu.active')) {
    if (submenu !== currentSubmenu)
      submenu.classList.remove('active');
  }
  currentSubmenu.classList.toggle('active');
});
.megamenu-submenu {
  border: 1px solid black;
  height: 1.6rem;
  box-sizing: border-box;
}
.megamenu-submenu.active {
  background-color: rgba(255, 0, 0, .3);
}
<div class="megamenu">
  <div class="megamenu-section">
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
  </div>
</div>

Подход 3

Аналогично подходу 2, но в цикле уже существующий NodeList submenus.

Это может снизить производительность (минимально) для слишком большого количества подменю, но обычно должно работать лучше, чем подход 2.

const megamenu = document.querySelector('.megamenu');
const menuSection = megamenu.querySelector('.megamenu-section');  
const submenus = document.querySelectorAll('.megamenu-submenu');

menuSection.addEventListener('click', evt => {
  const currentSubmenu = evt.target.closest('.megamenu-submenu');
  if (!currentSubmenu) return;
  
  for (const submenu of submenus) { // Only changed line
    if (submenu !== currentSubmenu)
      submenu.classList.remove('active');
  }
  currentSubmenu.classList.toggle('active');
});
.megamenu-submenu {
  border: 1px solid black;
  height: 1.6rem;
  box-sizing: border-box;
}
.megamenu-submenu.active {
  background-color: rgba(255, 0, 0, .3);
}
<div class="megamenu">
  <div class="megamenu-section">
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
    <div class="megamenu-submenu"></div>
  </div>
</div>
0
Oskar Grosser 12 Июн 2021 в 16:08

Для начала вы можете свести к минимуму свои циклы, изменив document.querySelectorAll('.megamenu-submenu'); на document.querySelectorAll('.megamenu-submenu.active'); и переместив его в событие on, чтобы оно рассчитывалось по событию, и таким образом вы выбираете только активные подменю. Тогда вы знаете, что у вашей текущей цели не будет .active, потому что вы удалили все .active, так что вы можете просто добавить вместо переключения.

После этого просто добавьте проверку, чтобы увидеть, есть ли e.target.closest('.megamenu-submenu').classList.contains('active'), и если да, просто удалите этот класс, в противном случае выполните все вышеперечисленное.

0
Ant 12 Июн 2021 в 13:20

Вы можете просто исключить выбранное подменю из общего удаления:

const clickedMenu = e.target.closest('.megamenu-submenu')
for (const submenu of submenus) {
    if (submenu != clickedMenu) {
        submenu.classList.remove('active');
    }
}
clickedMenu.classList.toggle('active');

Или сократите его до

const clickedMenu = e.target.closest('.megamenu-submenu')
for (const submenu of submenus) {
    submenu.classList.toggle('active', submenu == clickedMenu ? undefined : false);
}
0
Bergi 12 Июн 2021 в 14:28