同一元素绑定触摸与点击事件时出现的浏览器特定异常问题咨询
Unexpected Behavior When Binding Touch and Click Events to the Same Element
I’ve run into a browser-specific quirk when attaching both touch and click events to an element, paired with CSS state handling that misbehaves across devices. Let’s break down the scenario, the code involved, and how to resolve it.
The Core Issue
When using both touch events (like touchstart/touchend) and click events on the same button, you’ll notice two key problems:
- Mobile browsers often fire both events in sequence (touch first, then click after a 300ms delay), leading to duplicate actions.
- The CSS
:activestate behaves inconsistently between touch and mouse inputs, causing visual glitches (like the red overlay not hiding properly during touch interactions).
Relevant Code
Here’s the code that demonstrates the issue:
HTML
<button class="button"> </button>
CSS
html,body,div,button {margin:0;padding:0;border:0;background:transparent;outline:none;} .button { position: relative; width: 100px; height: 50px; background: #0f0; } .button:after { position: absolute; display:block; content: ''; background: #f00; top: 0; bottom:0; left: 0; right:0; margin: auto; display:none; } .button:not(:active):after { display:block; }
JavaScript
// Find target element var button = $("button") // Bind click event button.on('click', function() { console.log('Click event triggered'); }); // Bind touch event button.on('touchend', function() { console.log('Touch event triggered'); });
Why This Happens
- Dual Event Execution: Mobile browsers historically add a 300ms delay after touch events to detect double-taps for zoom. During this delay, they also fire a click event—so both touch and click handlers run, causing unintended duplicate actions.
- CSS State Mismatch: The
:activestate works differently for touch vs. mouse. On touch devices, the:activestate might not persist through the entire touch interaction, making the:not(:active):afterrule show the red overlay when it shouldn’t.
Practical Fixes
- Prevent Click After Touch: Add
event.preventDefault()to your touch handler to stop the browser from firing the subsequent click event. Note: This may disable other default behaviors like text selection.button.on('touchend', function(e) { e.preventDefault(); console.log('Touch event triggered'); }); - Use Unified Pointer Events: Replace separate touch/click events with
pointerdown/pointerupevents. These work seamlessly for both mouse and touch inputs, eliminating duplicate triggers:button.on('pointerup', function() { console.log('Pointer event triggered'); }); - Optimize CSS for Touch: Add
touch-action: manipulationto the button to remove the 300ms delay and improve:activestate consistency on touch devices:.button { /* existing styles */ touch-action: manipulation; }
Content of the question originates from Stack Exchange, question author jebunin




