🐛 fix: ignore taps that end a scroll on mobile

Some mobile browsers fire a click when a scroll gesture ends over a poster,
which opened it by accident. Swallow clicks from touches that moved or while
a scroll is still settling, so only real taps reach the app.
This commit is contained in:
WonderAliceUwU committed 2026-10-21 20:10:03 +02:00
1 parent 2bb0540ee6
commit 50bdbce1bd
2 files changed
+54

No files matched your search

+3
View File
@@ -2,6 +2,9 @@ import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App'
import { installTapGuard } from './tapGuard'
installTapGuard()
createRoot(document.getElementById('root')!).render(
<StrictMode>
+51
View File
@@ -0,0 +1,51 @@
// Some mobile browsers (notably iOS, and installed web apps) fire a click when
// a scroll gesture ends on top of a poster, which opens it by accident. This
// swallows clicks that come from a touch that moved or from a scroll that was
// still settling, so only real taps reach the app.
const TAP_SLOP_PX = 10
const SCROLL_SETTLE_MS = 120
const TOUCH_CLICK_WINDOW_MS = 800
export function installTapGuard() {
let startX = 0
let startY = 0
let moved = false
let lastTouchEndAt = 0
let lastScrollAt = 0
window.addEventListener('touchstart', (event) => {
const touch = event.touches[0]
startX = touch?.clientX ?? 0
startY = touch?.clientY ?? 0
moved = false
}, { capture: true, passive: true })
window.addEventListener('touchmove', (event) => {
const touch = event.touches[0]
if (touch && Math.hypot(touch.clientX - startX, touch.clientY - startY) > TAP_SLOP_PX) {
moved = true
}
}, { capture: true, passive: true })
window.addEventListener('touchend', () => {
lastTouchEndAt = Date.now()
}, { capture: true, passive: true })
// Scroll events don't bubble, so listen in the capture phase to see every
// scroll container (page, carousels, episode lists).
document.addEventListener('scroll', () => {
lastScrollAt = Date.now()
}, { capture: true, passive: true })
window.addEventListener('click', (event) => {
const now = Date.now()
if (now - lastTouchEndAt > TOUCH_CLICK_WINDOW_MS) {
return
}
if (moved || now - lastScrollAt < SCROLL_SETTLE_MS) {
event.preventDefault()
event.stopPropagation()
}
}, { capture: true })
}