DOM - время одновременных событий против setTimeout - PullRequest
0 голосов
/ 27 апреля 2018

Предположим, у меня есть элемент, содержащий несколько дочерних элементов, и я хочу запустить некоторый код всякий раз, когда мышь входит в контейнер или покидает его. Если я наивно напишу:

var onHover = function (el, f) {
    el.addEventListener('mouseover', function () {
        f(true);
    });
    el.addEventListener('mouseout', function () {
        f(false);
    });
};

Тогда я получаю желаемое поведение в некоторых случаях - в зависимости от характера обратного вызова f. Однако, когда мышь перемещается от дочернего элемента к дочернему в пределах контейнера, сразу запускается f(false), а затем f(true). Я не хочу, чтобы это произошло - я хочу, чтобы f запускался только тогда, когда мышь входит в контейнер или покидает его целиком, а не в стиле «пулемет», поскольку пользователь перетаскивает мышь над элементами, которые находятся внутри контейнера. .

Вот решение, которое я придумал:

var onHover = function (el, f) {
    var previousMouseover = false;
    var receivedMouseover = false;
    var pushing = false;
    var pushEv = function () {
        if (pushing) { return; }
        pushing = true;
        setTimeout(function () {
            pushing = false;
            if (previousMouseover !== receivedMouseover) {
                f(receivedMouseover);
                previousMouseover = receivedMouseover;
            }
        });
    };
    el.addEventListener('mouseover', function () {
        receivedMouseover = true;
        pushEv();
    });
    el.addEventListener('mouseout', function () {
        receivedMouseover = false;
        pushEv();
    });
};

Это решение, как и первое решение, предполагает и работает благодаря тому, что событие mouseout отправляется до события mouseover. Я также хотел бы знать, формально ли , что указано в какой-либо документации W3C, но это не тема этого вопроса, и даже если бы это было не так, было бы легко написать работающий алгоритм несмотря на это, установив две отдельные переменные, скажем receivedMouseover и receivedMouseout внутри обратных вызовов mouseover и mouseout, оба из которых затем проверяются внутри обратного вызова setTimeout.

Вопрос заключается в следующем: требуется ли, чтобы оба события mouseover и mouseout были обработаны до любых setTimeout обратных вызовов, зарегистрированных любым из этих событий?

Ответы [ 2 ]

0 голосов
/ 27 апреля 2018

Поскольку вы прикрепили прослушиватель событий к родительскому элементу, вы можете сравнить источник события (event.target) с родительским элементом (this или event.currentTarget), прежде чем предпринимать какие-либо действия. Вы можете сделать следующее:

var onHover = function (el, f) {
    el.addEventListener('mouseover', function (evt) {
        this === evt.target && f(true);
    });
    el.addEventListener('mouseout', function (evt) {
        this === evt.target && f(false);
    });
};

Большинство элементов пузырьков, так что в какой-то момент это может быть правильным способом сделать эту работу.

Редактировать: Как уже упоминалось в комментариях, события mouseover и mouseout могут быть проблематичными при некоторых обстоятельствах, например, когда для родительского элемента не определены отступы или поля, а дочерние элементы покрывают весь родительский элемент. Даже если они этого не делают, скорость мыши может быть достаточно высокой, чтобы движок JS не смог сэмплировать мышь над родительским элементом. Этот факт прекрасно объясняется в этой статье .

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

var onHover = function (el, f) {
    el.addEventListener('mouseenter', () => f(true));
    el.addEventListener('mouseleave', () => f(false));
};

Редактировать 2: Ну ... На самом деле есть безопасный способ использования mouseover и mouseout в этом конкретном случае. Речь идет об использовании свойства CSS pointer-events для дочерних элементов, которое запрещает им генерировать события для активности мыши.

var container = document.getElementById('container');

container.addEventListener('mouseover', function (ev) {
  console.log(container === ev.target);
});

container.addEventListener('mouseout', function (ev) {
  console.log(container === ev.target);
});
#container * {
  pointer-events: none
}
<div id="container">
  <div>
    <span>text</span>
  </div>
</div>
0 голосов
/ 27 апреля 2018

Используйте события mouseenter и mouseleave вместо mouseover и mouseout.

...