I'm trying to build a simple prototype in which I want to capture whether a user is either entering or leaving the website on desktop. Through 2 strips and a onmouseover/onmouseout I want to understand whether it's an inbound or outbound case. Hence the expected outcome is to get to the following sequences:
Outbound sequence:
MouseIn: lowerStrip - MouseOut: lowerStrip - MouseIn: upperStrip - MouseOut: upperStrip
Inbound sequence:
MouseIn: upperStrip - MouseOut: upperStrip - MouseIn: lowerStrip - MouseOut: lowerStrip
Unfortunately, the sequence produced by my prototype is not always the same - sometimes the 4 lines are printed to the console, sometimes only 2, and sometimes not even a single one. Seems like others have struggled with this same issue, be it in different languages and different contexts:
How do you Hover in ReactJS? - onMouseLeave not registered during fast hover over
onMouseEnter and onMouseLeave not functioning as expected
Mouseout and mouseleave not working
mouseenter and mouseleave in javascript not working
Regardless of some insights gathered from these solutions, I couldn't figure out how to actually make my solution more robuust, especially because I couldn't understand the actual root cause of the issue. Some where talking about speed being the issue, but even when scrolling over quite slow, the issue occurs.
Hence I'd like to understand what is actually causing this behaviour and what the best solution is to resolve this e.g. is it an issue in JS and could it be resolved with JS, is it an issue in JS and should it be resolved by CSS, is it another issue, ...
I attached my example below. The idea is to hover over the two strips, either coming from the bottom or the top. This should trigger the sequence mentioned earlier.
Thanks for your input.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Template</title>
<style>
</style>
<style>
</style>
<script type='text/javascript'>
function init() {
document.getElementById('upperStrip').setAttribute('style', 'position: absolute; margin: 0 !important; top: 0px; width: ' + window.innerWidth +'px; border: 5px solid #ff0000');
document.getElementById('lowerStrip').setAttribute('style', 'position: absolute; margin: 0 !important; top: 10px; width: ' + window.innerWidth +'px; border: 5px solid #000000');
}
function onMouseIn(x) {
console.log("MouseIn: ", x.id);
}
function onMouseOut(x) {
console.log("MouseOut: ", x.id);
}
</script>
</head>
<body onload="init()">
<hr id='upperStrip' onmouseover="onMouseIn(this)" onmouseout="onMouseOut(this)">
<hr id='lowerStrip' onmouseover="onMouseIn(this)" onmouseout="onMouseOut(this)">
</body>
</html>
EDIT: 04/07/2020
Meanwhile I figured out the root cause:
"The mousemove event triggers when the mouse moves. But that doesn’t mean that every pixel leads to an event. The browser checks the mouse position from time to time. And if it notices changes then triggers the events. That means that if the visitor is moving the mouse very fast then some DOM-elements may be skipped." (https://javascript.info/mousemove-mouseover-mouseout-mouseenter-mouseleave)
"In case of fast mouse movements, intermediate elements may be ignored, but one thing we know for sure: if the pointer “officially” entered an element (mouseover event generated), then upon leaving it we always get mouseout."
Based on that, I'm now looking for a solution that is robust for my particular case. Waiting for the events seems an option, but I can also not wait 'too long' because I'm trying to register an exit intent. Also I need to at least be able to generate a onmouseover
before even having a chance to capture the onmouseout
.
I found a source which mentioned that: "Mouse does not report its position by every pixel it passes, there are 20ms intervals between reports. If you manage to cross your control within this interval, it will catch no mouse events at all."
Hence I should be able to capture the 4 events in ~80ms? The question is how...