Touch delay is a thing of the past, but accidental zooming is here to ruin your day. Ever tapped a button quickly on iOS and experienced a zoom instead of two taps? You're in the right place.
Just use this helper function to preventDefault
on double taps, while still
allowing for pinch to zoom and rapid scrolling.
// Ensure touches occur rapidly
const delay = 500
// Sequential touches must be in close vicinity
const minZoomTouchDelta = 10
// Track state of the last touch
let lastTapAt = 0
let lastClientX = 0
let lastClientY = 0
export default function preventDoubleTapZoom(event) {
// Exit early if this involves more than one finger (e.g. pinch to zoom)
if (event.touches.length > 1) {
return
}
const tapAt = new Date().getTime()
const timeDiff = tapAt - lastTapAt
const { clientX, clientY } = event.touches[0]
const xDiff = Math.abs(lastClientX - clientX)
const yDiff = Math.abs(lastClientY - clientY)
if (
xDiff < minZoomTouchDelta &&
yDiff < minZoomTouchDelta &&
event.touches.length === 1 &&
timeDiff < delay
) {
event.preventDefault()
// Trigger a fake click for the tap we just prevented
event.target.click()
}
lastClientX = clientX
lastClientY = clientY
lastTapAt = tapAt
}
const TapAsFastAsYouWantButton = (children, ...rest) =>
<button {...props} onTouchStart={preventDoubleTapZoom}>
{children}
</button>
const Game = () =>
<TapAsFastAsYouWantButton onClick={fireLasers}>
Fire Lasers
</TapAsFastAsYouWantButton>
Worked for me with some remaks. It is not allowed to add event listener on React's onTouchStart due to it adds passive event listener. You have to use refs for target container and add event listener like
rootRef.current.addEventListener('touchstart', onTouchStart, {passive: false})
.The other way you can make it easier is to add event listener to
document.body
.Leaving React Hooks Typescript alternative: