Отклонить выпадающий список при внешнем щелчке, используя роли vanilla JavaScript и ARIA

#javascript

#javascript

Вопрос:

Я пытался создать очень простое выпадающее меню, но не могу правильно скрыть часть внешнего щелчка. Я прошел несколько итераций, но всегда оказываюсь неспособным правильно отклонить его или закрыть другие открытые выпадающие списки, когда активирован другой. Вот мой последний вариант.

 function Menu(trigger) {
  let expanded = trigger.getAttribute("aria-expanded") === "true" || false;
  let menu = trigger.nextElementSibling;

  trigger.setAttribute("aria-expanded", !expanded);
  menu.hidden = !menu.hidden;
}

const menuTriggers = document.querySelectorAll(".menu-trigger");

menuTriggers.forEach((menuTrigger) => {
  menuTrigger.addEventListener("click", () => {
    Menu(menuTrigger);
  });
});  
 <button class="menu-trigger" aria-expanded="false">Menu</button>
<ul class="menu" hidden>
  <li class="menu-item">
    <a class="menu-link" href="">Books</a>
  </li>
  <li class="menu-item">
    <a class="menu-link" href="">Authors</a>
  </li>
  <li class="menu-item">
    <a class="menu-link menu-link-separator" href="">Genres</a>
  </li>
  <li class="menu-item">
    <a class="menu-link" href="">Themes</a>
  </li>
</ul>  

Ответ №1:

Вам нужно будет прослушать событие наведения курсора мыши на объект window, чтобы обнаружить щелчок и закрыть выпадающее меню, только если event.target не содержится в самом выпадающем меню.

 function Menu(trigger)
{
    let expanded = trigger.getAttribute("aria-expanded") === "true" || false;
    let menu = trigger.nextElementSibling;
    trigger.setAttribute("aria-expanded", !expanded);
    menu.hidden = !menu.hidden;
    if(!menu.hidden)
    {
        setTimeout(() =>
        {
            let eventType = "mousedown";
            let handler = (e) =>
            {
                if (trigger !== e.target amp;amp; !menu.contains(e.target))
                {
                    Menu(trigger);
                }
                window.removeEventListener(eventType, handler, false);
            };
            window.addEventListener(eventType, handler, false);
        }, 0);
    }
}

const menuTriggers = document.querySelectorAll(".menu-trigger");
menuTriggers.forEach((menuTrigger) =>
{
    menuTrigger.addEventListener("click", () =>
    {
        Menu(menuTrigger);
    });
});  
 <div style="display:inline-block">
<button class="menu-trigger" aria-expanded="false">Menu</button>
<ul class="menu" hidden>
  <li class="menu-item">
    <a class="menu-link" href="https://amazon.com">Books</a>
  </li>
  <li class="menu-item">
    <a class="menu-link" href="">Authors</a>
  </li>
  <li class="menu-item">
    <a class="menu-link menu-link-separator" href="">Genres</a>
  </li>
  <li class="menu-item">
    <a class="menu-link" href="">Themes</a>
  </li>
</ul>
</div>  

Комментарии:

1. Спасибо за ваш ответ. Я попытаюсь обработать внешний щелчок внутри Menu() функции, чтобы установить более четкое определение компонента.

2. Нет проблем, я отредактировал ответ, переместив код в Menu() функцию.

3. Попробовал прямо сейчас. Спасибо за советы! Из того, что я мог собрать setTimeout() , это не обязательно; это правильно? И еще одно условие должно быть добавлено как часть handler() , чтобы разрешить переход по ссылкам : !trigger.contains(e.target) amp;amp; !menu.contains(e.target) .

4. Вы можете обнаружить, что в некоторых ситуациях setTimeout необходим. Это предотвращает нажатие кнопки от запуска события mousedown во время того же щелчка. Странно, но я видел, как это происходит в некоторых браузерах. ul Элемент представляет собой блочный элемент, который незаметно охватывает всю ширину экрана. Чтобы решить эту проблему, я обернул html внутри inline-block . Вы разберетесь в деталях; для вас это было только начало.

5. Еще раз спасибо за помощь. Действительно, кажется, что необходимо предотвратить запуск нескольких событий, либо хотя setTimeout() , либо различными условными операторами.