1

Perhaps my Google-fu is really poor today, but I've been unable to find any explanation for this strange behavior I'm noticing.

I've got a mousemove listener event on a parent node, but sometimes the event.target is a child node. On Chrome (v79 and also surprisingly Safari), the event.offsetX/Y value is consistent, regardless if the target is the root node or a child node. In Firefox (v72.0.1) and non-Chromium Edge, the event.offsetX/Y is suddenly relative to the child node, if the child node is the target. This is throwing off my event handling function, as it needs the mouse position relative to the root listener node.

I've looked at using event.layerX/Y, which is consistently similar to offsetX/Y on the parent node, but not always equal when it is the child node.

I cannot cheat and use CSS's pointer-events:none, because I still have click handlers on these child nodes.

Is there an easy way to guarantee the event.target is the node I set the event listener on, or to always get the mouse position relative to the parent/listener node?

1

1 Answer 1

2

The behavior you describe as Firefox's one is the right one, since specs ask that .offsetN values are relative to the "target node".
My Chrome does by the way also expose this behavior.

But you can reproduce the one you want by dispatching a new synthetic Event based on the real one. This way all browsers will agree there is only one target node, and the .offsetN values will always be relative to it.

// static logger
const _log = document.getElementById('_log');
const log = (...vals) => {
 _log.textContent += vals.map(v => JSON.stringify(v)).join(' – ') + '\n';
}
const clearLog = () => { _log.textContent = "";}

// actual code
document.getElementById('parent').onmousemove = function(e) {
  // new true event
  if(e.isTrusted) { clearLog(); }
  // in case we are not the true target
  if(e.target !== e.currentTarget) {
    // create a synthetic event
    const synth = new MouseEvent('mousemove', { clientX: e.clientX, clientY: e.clientY});
    // dispatch it *synchronously* on us
    e.currentTarget.dispatchEvent(synth);

    // to show it would have been wrong in FF
    log('originals ', e.offsetX, e.offsetY);
    return;
  }
  // here the values are consistent
  log(e.isTrusted ? 'originals ' : 'synthetics', e.offsetX, e.offsetY);
};
#_log {
  background: #EEE;
  height: 3em;
  padding: 2px;
}
#parent {
  border: 1px solid;
  width: 400px;
  height: 200px;
}
#child {
  border: 1px solid;
  transform: translate(150px, 50px);
  width: 100px;
  height: 100px;
}
<pre id="_log"></pre>
<div id="parent">
  <div id="child">
  </div>
</div>

Sign up to request clarification or add additional context in comments.

8 Comments

Is there a venue to report this departure from spec?
@mix3d not sure what you ask here... Do you want to ask specs to change so that these props are relative to currentTarget? That would probably add some complexity for something authors can already do themselves, as demonstrated.
If Chrome is off spec, that bug should be reported, no?
Ah ok, then yes, you can open an issue on chromium's bug-tracker. Add as much information as you can, and once again, on my Chrome 79 on macOs, I have the same correct behavior on Chrome and FF. Though I guess this one is actually the same.
Seems related, but more about padding on an element then child elements. I'll use your code in a codepen with author link, and submit tomorrow.
|

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.