It appears that different browsers have varying behaviors in this scenario...
When testing on Android, both Firefox and Chrome handle the click event differently. In Firefox, only the touchend
event fires after the initial click, whereas Chrome sometimes triggers the touchcancel event as well (especially when text in the button is selected). I have not had a chance to test Safari yet.
To address this issue, listen for the touchend event instead of the mouseup event. This will ensure that the event still fires even if you move the cursor outside of the element after initially clicking. Additionally, be aware of the touchcancel event, which may occur slightly before the user releases their touch.
In order to prevent both the click event and the touchend/touchcancel events from firing simultaneously, consider debouncing your event handler. This means that the event handler will only run once within a specified time frame. It may also be necessary to prevent the default behavior of the contextmenu event.
// quick logs:
['click', 'touchstart', 'touchend', 'touchcancel', 'contextmenu'].forEach(type => {
btn.addEventListener(type, e => console.log(e.type));
});
// necessary code:
btn.oncontextmenu = e => e.preventDefault(); // prevent context menu on long touch
btn.onclick = btn.ontouchend = btn.ontouchcancel = debounce(e => {
console.log('Successfully 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>
Keep in mind that preventing the default action of the button's click event may affect other functionalities (e.g., submitting a form if the button is inside a <form>). Proceed with caution.