After some tries, it seems that all browsers do not behave the same here...
In both Firefox and Chrome on Android, the click event gets discarded by the contextmenu event, I couldn't test on Safari yet. But, in Firefox the only event to fire after is the touchend
one, while on Chrome it seems the touchcancel one fires sometimes (apparently when the text in the button gets selected).
So to get this, you'd listen for the touchend event which unlike the mouseup event will also fire if you do move the cursor outside of the element after you started to click, but won't fire if you started the click outside and then moved the cursor over the element, and for the touchcancel event, which may fire a bit before the user released there touch (but it is the latest event we can get...).
Now, to avoid both click and touchend/touchcancel events to fire on the same action, you'd need to debounce your event handler (i.e force it to run only once in a given lapse of time), and probably also to prevent the default behavior of the contextmenu event.
// simple logs:
['click', 'touchstart', 'touchend', 'touchcancel', 'contextmenu'].forEach(type => {
btn.addEventListener(type, e => console.log(e.type));
});
// what you need:
btn.oncontextmenu = e => e.preventDefault(); // prevent the context menu on long touch
btn.onclick = btn.ontouchend = btn.ontouchcancel = debounce(e => {
console.log('Correctly handled the click event');
}, 100);
function debounce(fn, timeout=0) {
let flag = false;
return function() {
if (!flag) {
flag = true;
setTimeout(_ => flag = false, timeout);
fn.call(...arguments);
}
};
}
<button id="btn">
click me
</button>
Beware though that the default action of the button's click won't happen either (for instance if this button was in a <form>, the form won't get submitted in case of long-touch.