Mouseleave, вызванный щелчком

У меня есть абсолютно позиционированный div, и я пытаюсь отслеживать, когда мышь перемещается по нему, и когда мышь уходит. К сожалению, нажатие на текст в поле иногда вызывает событие mouseleave.

DEMO: js скрипка

Как я могу предотвратить это?

JS

let tooltip = document.createElement('div');
tooltip.innerHTML = 'HELLO WORLD';
tooltip.setAttribute('class', 'tooltip');
tooltip.style.display = 'none';

tooltip.onclick = evt => {
    console.log('click')
    evt.stopPropagation();
}
tooltip.ondblclick = evt => {
    console.log('double click')
    evt.stopPropagation();
}

tooltip.onmouseenter = () => {
    console.log('tooltip mouse OVER');
}

tooltip.onmouseleave = () => {
    console.log('tooltip mouse OUT')
}

tooltip.style.left = '290px';
tooltip.style.top = '50px';
tooltip.style.display = 'block';
document.body.appendChild(tooltip);

HTML

<div style="width: 300px; height: 300px; background-color: lightblue">

</div>

CSS

.tooltip {
    position: absolute;
    /*display: none;*/
    left: 100;
    top: 100;
    min-width: 80px;
    height: auto;
    background: none repeat scroll 0 0 #ffffff;
    border: 1px solid #6F257F;
    padding: 14px;
    text-align: center;
}

Ответ 1

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

Я бы предложил обойти эту проблему, проверив, находится ли мышь по всему элементу в момент запуска события:

tooltip.onmouseleave = (e) => {
    if (tooltip === document.elementFromPoint(e.clientX, e.clientY)) {
        console.log('false positive');
        return;
    }
    console.log('tooltip mouse OUT')
}

Недостатком является то, что когда окно браузера теряет фокус, это также считается ложным. Если это проблема для вас, то выберите этот ответ.

Ответ 2

Ранее я просматривал ответы и комментарии, но недавно нашел способ проверить, было ли ошибочно запущено событие mouseleave

Я добавил чек в мой обработчик mouseleave:

private handleMouseLeave(event: MouseEvent) {
    if(event.relatedTarget || event.toElement){
        // do whatever
    }
    // otherwise ignore
}

Из моего тестирования в Chrome v64 оба этих значения будут null всякий раз, когда быстрый щелчок вызывает событие mouseleave. relatedTarget предназначен для более старой совместимости с браузером

Примечание. Оба эти значения также будут null, если мышь покинет элемент target и войдет в браузер (например, вкладки, меню и т.д.) или покинет окно браузера. Для моих целей это не было проблемой, так как это скользящее меню, с которым я работаю, и оставить окно браузера не должно закрывать меню в моем конкретном случае.

Примечание: последняя версия Firefox (февраль 2018), кажется, запускает mouseleave при каждом нажатии моего меню! Придется заглянуть в него

Ответ 3

Я также столкнулся с этой ошибкой. В моем случае я добавил флажки, завернутые в список, и завернул список в div. Я также использовал некоторые элементы списка, которые были тегами <hr>. Если вы быстро нажмете флажки и метки, вы иногда запускаете событие mouseleave на обертке div. Это не должно происходить, так как все щелкнутые элементы являются дочерними элементами div.wrapper.

...
  wrapper.addEventListener(
    'mouseleave',
    (e) => {
      logger('mouseleave fired');
      console.log('mouseleave fired', e);
    },
    false
  );
...

jsfiddle demo

Здесь gif воспроизведения. Нажмите внутри области подсказки (предоставленной с некоторой интенсивностью и движением), щелкните события из метки и окна ввода, и вы увидите, что два события mouseleave срабатывают по ошибке, а затем третий, когда мышь действительно покидает синюю область.

Пример воспроизведения

Ответ 4

Ответ @trincot для меня почти сработал. В моем случае я имею дело с поповерами. Когда я нажимаю на кнопку, она запускает всплывающее окно, отображаемое поверх кнопки запуска. Поэтому document.elementFromPoint(e.clientX, e.clientY) возвращает элемент popover, а не кнопку запуска. Вот как я решил это:

mouseleave(ev: MouseEvent) {
    const trigger: HTMLElement = document.getElementById('#myTrigger');
    const triggerRect = trigger.getBoundingClientRect();
    const falsePositive = isWithingARect(ev.clientX, ev.clientY, triggerRect);

    if (!falsePositive) {
        // do what needs to be done
    }
}

function isWithingARect(x: number, y: number, rect: ClientRect) {
  const xIsWithin = x > rect.left && x < rect.right;
  const yIsWithin = y > rect.top && y < rect.bottom;
  return xIsWithin && yIsWithin;
}

Ответ 5

var trackmouseup = null;

$('.box').mouseup(function(event){
    if(trackmouseup){
        clearTimeout(trackmouseup);
    }
    trackmouseup = setTimeout(function(){
        trackmouseup = null;
    }, 2); //it must be 2ms or more

});


$('.box').mouseleave(function(event){
    //if this event is triggered by click, there must be a mouse up event triggered 2ms ago
    if(trackmouseup){
        return;
    }

    //to do something
});