I expect DOM event handling to be synchronous, regardless of the internal sequence of events in a browser.
For reasons I don't understand the click handler is massively delayed (varying between 30-90ms on my machine, Macbook Pro M1 Max, Chrome 126.0.6478.63), so both the code in setTimeout
and in requestAnimationFrame
run before the click handler runs.
Can someone explain this behavior?
See this MVCE:
const css = `:host(:focus-within) div { outline: 1px solid blue; display: inline-block; }`;
const sheet = new CSSStyleSheet();
sheet.replaceSync(css);
customElements.define(
"a-b",
class extends HTMLElement {
input = document.createElement("input");
btn = Object.assign(document.createElement("button"), {
textContent: "Click me"
});
constructor() {
super().attachShadow({ mode: "open" }).adoptedStyleSheets.push(sheet);
this.shadowRoot.append(this.input, this.btn);
this.addEventListener("focusin", this.handleFocusIn);
this.addEventListener("click", this.handleClick);
}
handleFocusIn() {
output.value += `focusin: ${Date.now()}\n`;
setTimeout(() => {
// How is this executed before the click listener runs??
output.value += `timeout: ${Date.now()}\n`;
}, 0);
requestAnimationFrame(() => {
// How is this executed before the click listener runs??
output.value += `rAF: ${Date.now()}\n`;
});
}
handleClick() {
output.value += `click: ${Date.now()}\n----------------------\n`;
}
}
);
textarea {
height: 12em;
margin-top: 2em;
width: 16em;
}
<a-b></a-b>
<br>
<textarea id="output"></textarea>
The delay you see corresponds to the period you have the mouse button down. The click
event only triggers when the mouse button is released. As Mozilla contributors write:
click
fires after both themousedown
andmouseup
events have fired, in that order.
In case of a mouse button triggering these events, the time you need to release that button again is typically a few tenths of a second, which explains your measurements.
Depending on your use case it might be more useful to listen to the mousedown
event, which will occur just before the focusin
event.