Почему $ ("a: focus"). Length имеет значение 0, если оно не заключено в setTimeout ()? - PullRequest
0 голосов
/ 07 декабря 2018

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

var $nav = $(".navigation nav.main"),
    $navItems = $nav.find("a");

$navItems.on("blur", function() {
    if ($nav.find("a:focus").length === 0) {
        closeMenu();
    }
});

То, как я ожидаю, что это сработает, выглядит следующим образом:

  1. Всякий раз, когда какая-либо ссылка в меню теряет фокус ...
  2. Если в меню есть нет ссылок, имеющих фокус, закройте меню

Но на самом деле происходит $(nav.find("a:focus").length) всегда 0, даже когда я непосредственно вижу на экране, что в меню действительно есть ссылка с фокусом.

Однако, если я оберну setTimeout() вокруг условного выражения, то это сработает так, как я.будет ожидать:

var $nav = $(".navigation nav.main"),
    $navItems = $nav.find("a");

$navItems.on("blur", function() {
    setTimeout(function() {
        if ($nav.find("a:focus").length === 0) {
            closeMenu();
        }
    });
});

Теперь $nav.find("a:focus").length повышается до 1 каждый раз, когда я нажимаю Tab , до тех пор, пока я не нажму за пределами меню, в этот момент он оценивается как 0 и закрывает его.

Это именно то, как я хочу, чтобы это работало, но зачем setTimeout() необходимо?

1 Ответ

0 голосов
/ 07 декабря 2018

Почему это не работает?

Как отмечает epascarello в комментариях, ваш blur происходит до того, как следующий элемент будет в фокусе.

Почему setTimeout(), даже с задержкой 0ms, работает?

I , полагаю , это связано с механизмом рендеринга.Когда вы делаете setTimeout(), функция «в очереди» позади обновления пользовательского интерфейса движка рендеринга (которое фокусируется на следующем элементе).Несмотря на отсутствие разницы в время , setTimeout() обеспечивает проверку после обновления пользовательского интерфейса.

Порядок событий:

1. Fire blur event
2. Update UI

Порядок событий с setTimeout():

1. Fire blur event (queue new function) ──┐
2. Update UI                              |
3. Run the queued function    <───────────┘

Есть ли альтернатива?

Событие blur relatedTarget указывает, какой элемент будет получать фокус.

Зная элемент, который должен получить фокус, вы можете использовать jQuery .filter(), чтобы определить, является ли он элементом навигации или нет.

var $nav = $(".navigation nav.main"),
    $navItems = $nav.find("a");

$navItems.on("blur", function(e) {
  var $focusedElement = $(e.relatedTarget);
  var isNavItem = !!$navItems.filter($focusedElement).length;
  if (!isNavItem)
    console.log("The focused element is not a nav item.");
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="navigation">
  <nav class="main">
    <a href="#">Link 1</a>
    <a href="#">Link 2</a>
  </nav>
</div>

<a href="#">Non-nav link</a>
...