Я знаю, что это распространенный вопрос, но ни один из ответов, которые я видел, не решает мою проблему, извиняюсь, если я пропустил один, и это, очевидно, можно удалить / пометить как дубликат ...
Разметка
<div class = "has-dropdown">
<button class = "js-dropdown-trigger">
Dropdown
</button>
<div class = "dropdown">
<div class = "dropdown__item">
Some random text with a <a href = "#" class = "stop-propagation">link</a> in it.
</div>
<div class = "dropdown__divider"></div>
<div class = "dropdown__item">
<a href = "#">Item One</a>
</div>
<div class = "dropdown__item">
<a href = "#">Item Two</a>
</div>
<div class = "dropdown__item">
<a href = "#">Item Three</a>
</div>
</div>
</div>
Сценарий
$('.has-dropdown').off().on('click', '.js-dropdown-trigger', (event) => {
const $dropdown = $(event.currentTarget).next('.dropdown');
if (!$dropdown.hasClass('is-active')) {
$dropdown.addClass('is-active');
} else {
$dropdown.removeClass('is-active');
}
});
$('.has-dropdown').on('focusout', (event) => {
const $dropdown = $(event.currentTarget).children('.dropdown');
$dropdown.removeClass('is-active');
});
Укладка
.has-dropdown {
display: inline-flex;
position: relative;
}
.dropdown {
background-color: #eee;
border: 1px solid #999;
display: none;
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
width: 300px;
margin-top: 5px;
}
.dropdown.is-active {
display: flex;
}
.dropdown__item {
padding: 10px;
}
.dropdown__divider {
border-bottom: 1px solid #999;
}
Скрипка
http://jsfiddle.net/joemottershaw/3yzadmek/
Это невероятно просто: щелчок по js-dropdown-trigger переключает выпадающий класс is-active, щелчок за пределами контейнера has-dropdown удаляет раскрывающийся класс is-active.
За исключением того, что я ожидал, что сосредоточение внимания на элементе-потомке (щелчок или вкладка) элемента has-dropdown будет означать, что обработчик событий focusout не должен запускаться, поскольку вы все еще сосредоточены на элементе-потомке контейнера has-dropdown.
The
focusoutevent is sent to an element when it, or any element inside of it, loses focus. This is distinct from the blur event in that it supports detecting the loss of focus on descendant elements
Я знаю, что могу удалить обработчик событий focusout и использовать что-то вроде:
$(document).on('click', (event) =>{
const $dropdownContainer = $('.has-dropdown');
if (!$dropdownContainer.is(event.target) && $dropdownContainer.has(event.target).length === 0) {
$dropdownContainer.find('.dropdown').removeClass('is-active');
}
});
Это работает, но если вы нажмете на триггер, а затем перейдете по ссылкам, при переходе по последней ссылке выпадающий список все равно будет виден. Просто изо всех сил пытаюсь найти лучшее решение, чтобы сохранить доступность вещей.
Я хочу придерживаться метода focusout, если это вообще возможно.
Обновлено на основе ответа даршанаги
Хотя обновленный сценарий работает для отдельных элементов, добавление других элементов в body приводит к тому, что focusout больше не работает должным образом. Я думаю, это потому, что утверждение if кажется верным, даже когда фокус применяется к любому элементу после контейнера has-dropdown, а не только к потомкам? Причина, если вы хотите обновить HTML и добавить больше фокусируемых элементов, таких как ввод после раскрывающегося списка. При переходе от последнего фокусируемого элемента из контейнера has-dropdown к входу раскрывающийся список остается активным. Он работает только в том случае, если раскрывающийся список является последним элементом в DOM и срабатывает только тогда, когда фокус полностью теряется на DOM.

Вы почти закончили свой код, но я считаю, что нужно немного больше прояснить, как focusout работает с изначально не фокусируемыми элементами (например, div, p) и их потомками, которые могут быть фокусируемыми элементами (входы, якоря ).
Когда контейнер привязан к событию focusout, которое содержит фокусируемые элементы, это событие focusout запускается каждый раз любой из его фокусируемых дочерних элементов теряет фокус - это может быть с помощью навигации с клавиатуры или щелчком по другому дочернему элементу или по самому контейнеру. Я установил скрипт, который демонстрирует это: https://jsfiddle.net/darshanags/v5gk2cz8/ - сообщения консоли отправляются каждый раз, когда контейнер получает или теряет фокус.
В: Итак, почему меню скрывается в примере, приведенном в вопросе?
А: Меню становится видимым, когда вы нажимаете кнопку, нажатие на кнопку заставляет кнопку фокусироваться на самой себе. Но как только вы щелкаете меню или якорь внутри элемента меню, кнопка теряет фокус - это, в свою очередь, запускает событие focusout родительского элемента и заставляет меню скрывать себя. Это происходит потому, что событие focusout поддерживает восходящую цепочку событий.
В: Как нам это обойти?
A1.1: Мы делаем родительский элемент доступным для фокусировки, задав ему tabindex:
<div class = "has-dropdown" tabindex = "0">
Это касается нескольких важных вещей:
div контур фокуса - это, в свою очередь, увеличивает доступность элемента.div - в этом примере это будет focusout. Если бы tabindex был опущен, нам нужно было бы добавить дополнительный JavaScript, чтобы компенсировать потерю функциональности.A1.2: В обработчике событий focusout мы проверяем, является ли элемент, который получает фокус, потомком родительского элемента, если это так, мы не удаляем класс is-active из меню.
Полный пример кода:
$('.has-dropdown').off().on('click', '.js-dropdown-trigger', (event) => {
const $dropdown = $(event.currentTarget).next('.dropdown');
if (!$dropdown.hasClass('is-active')) {
$dropdown.addClass('is-active');
} else {
$dropdown.removeClass('is-active');
}
});
$('.has-dropdown').on('focusout', function(event) {
const $reltarget = $(event.relatedTarget);
const $currenttarget = $(event.currentTarget);
const $dropdown = $currenttarget.children('.dropdown');
// remove 'is-active' class only if the element
// that is gaining focus is not a child of the parent.
// parent = div.has-dropdown
if (!$reltarget.closest('.has-dropdown').is($currenttarget)) {
$dropdown.removeClass('is-active');
}
});.has-dropdown {
display: inline-flex;
position: relative;
}
.dropdown {
background-color: #eee;
border: 1px solid #999;
display: none;
flex-direction: column;
position: absolute;
top: 100%;
left: 0;
width: 300px;
margin-top: 5px;
}
.dropdown.is-active {
display: flex;
}
.dropdown__item {
padding: 10px;
}
.dropdown__divider {
border-bottom: 1px solid #999;
}<script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class = "has-dropdown" tabindex = "0">
<button class = "js-dropdown-trigger">
Dropdown
</button>
<div class = "dropdown">
<div class = "dropdown__item">
Some random text with a <a href = "#" class = "stop-propagation">link</a> in it.
</div>
<div class = "dropdown__divider"></div>
<div class = "dropdown__item">
<a href = "#">Item One</a>
</div>
<div class = "dropdown__item">
<a href = "#">Item Two</a>
</div>
<div class = "dropdown__item">
<a href = "#">Item Three</a>
</div>
</div>
</div>
<div class = "has-dropdown" tabindex = "0">
<button class = "js-dropdown-trigger">
Dropdown
</button>
<div class = "dropdown">
<div class = "dropdown__item">
Some random text with a <a href = "#" class = "stop-propagation">link</a> in it.
</div>
<div class = "dropdown__divider"></div>
<div class = "dropdown__item">
<a href = "#">Item One</a>
</div>
<div class = "dropdown__item">
<a href = "#">Item Two</a>
</div>
<div class = "dropdown__item">
<a href = "#">Item Three</a>
</div>
</div>
</div>
<input name = "tf" type = "text"/>Я раздвоил исходную скрипку и изменил ее, чтобы показать это в действии. Вы можете найти измененную скрипку здесь: http://jsfiddle.net/darshanags/60jnusvk/.
Другая полезная информация:
Я использую event.relatedTarget для определения элемента, который получает фокус каждый раз, когда запускается событие focusout. Более подробную информацию о event.relatedTarget можно найти здесь: https://api.jquery.com/event.relatedTarget/.
Обновлять
Я реорганизовал часть исходного кода, теперь он намного проще: http://jsfiddle.net/darshanags/60jnusvk/24/. Я оставлю эту скрипку и оригинал для справки.
Я изменил оператор if в обработчике событий focusout на следующий: if (!$(event.currentTarget).has(event.relatedTarget).length) { // Hide }}. Я считаю, что сейчас это работает, и я думаю, что вы даже можете удалить атрибут tabindex, и он работает? jsfiddle.net/joemottershaw/scnx4od9 Если вы думаете, что это тоже, не могли бы вы обновить свой ответ новым оператором if, и я отмечу его как верный наградой!
@ Джо, я смотрю на твою модифицированную скрипку: jsfiddle.net/joemottershaw/scnx4od9. Выпадающие меню работают нормально, когда я просматриваю элементы с помощью клавиатуры, но если я щелкаю где-нибудь еще в меню, оно закрывается - я не думаю, что это желаемое поведение? Я использую firefox
Ах, вы были бы правы, это тоже нежелательное поведение. Вы понимаете, что я имею в виду, говоря о том, что ваш оператор if сохраняет раскрывающийся список активным, хотя на странице есть другие элементы? Может быть, ему нужна комбинация обоих операторов if: один, чтобы он оставался открытым, если щелкнуть внутри has-dropdown, а также если сфокусирован потомок has-dropdown?
Я добавил еще одну проверку в свою скрипку, чтобы увидеть, находимся ли мы в одном меню - jsfiddle.net/darshanags/60jnusvk/20. Я считаю, что это удовлетворяет критериям вашего исходного сообщения и нескольких меню.
Это нормально, если на странице есть только раскрывающиеся списки, как я упоминал ранее, если вы добавляете ввод после двух раскрывающихся списков, когда вы фокусируетесь с последнего раскрывающегося списка на ввод, раскрывающийся список все еще остается активным? Кроме того, не всегда ли && $currenttarget.hasClass('has-dropdown') будет has-dropdown, поскольку это является текущая цель?
Да, у меня твоя рабочий пример работает. Но я придумал более простой фрагмент кода, за исключением большинства проверок, которые я реализовал ранее. Новая рабочий пример здесь: jsfiddle.net/darshanags/60jnusvk/24
Отлично, если бы вы могли обновить свой ответ последним кодом на случай, если рабочий пример когда-нибудь будет удалена или выйдет из строя и т. д. Я отмечу правильный ответ и награжу награду, я ценю постоянную помощь, спасибо :)
Нет проблем - я обновил свой ответ. И я также подробно рассказал, почему я использую tabindex в элементе (ах) div.
Я не знал, что
focusoutвызывали каждый раз так! Тем не менее, это хороший шаг в правильном направлении, так что спасибо вам за это! Однако это работает только в том случае, если это единственный элемент в теле, сейчас я дополню свой ответ объяснением.