fix(ux): zuverlässige Scroll-Wiederherstellung für Infinite-Scroll-Listen
Zwei Probleme bei der großen Rennmausliste (und allen Infinite-Scroll-Listen) nach Zurück-Navigation behoben: 1) Event- statt Timer-basiert: Die Scroll-Wiederherstellung hängt sich jetzt per ResizeObserver an die Höhenänderung der Seite (Liste ist wieder da) und springt dann zur gemerkten Position — statt auf einen festen Timer zu warten. Bricht bei eigener Scroll-Eingabe ab; Sicherheitsnetz nach 15 s. 2) Infinite-Scroll baut Höhe wieder auf: useInfiniteList merkt sich (optionaler restoreKey) die geladene Seitenzahl je Query und lädt sie nach einer Zurück- Navigation wieder nach — sonst war die Liste nur 1 Seite hoch und die Position unerreichbar. Aktiviert für Rennmäuse, Kontakte, Würfe, Becken, Anfragen, Verträge. tsc/eslint/vitest grün; e2e (Listen + Navigation, 41) grün. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,9 +15,10 @@ import { useCallback, useEffect, useRef } from 'react'
|
||||
import { useLocation, useNavigationType } from 'react-router-dom'
|
||||
|
||||
const PREFIX = 'scroll:'
|
||||
// Etwas großzügig, damit auch langsam (kalt) nachladende Listen + Dev-HMR-Updates die Position
|
||||
// noch erreichen, während der Inhalt in die Höhe wächst.
|
||||
const RESTORE_DEADLINE_MS = 4000
|
||||
// Sicherheitsnetz: Beobachtung der Höhenänderungen nach dieser Zeit beenden (kein Dauer-Listener,
|
||||
// falls die Zielhöhe nie erreicht wird). Die eigentliche Wiederherstellung läuft EVENT-basiert
|
||||
// (ResizeObserver), nicht über einen Polling-Timer.
|
||||
const RESTORE_OBSERVE_CAP_MS = 15000
|
||||
|
||||
export function useScrollRestoration() {
|
||||
const { pathname } = useLocation()
|
||||
@@ -55,34 +56,35 @@ export function useScrollRestoration() {
|
||||
if (!Number.isFinite(y) || y <= 0) return
|
||||
|
||||
restoringRef.current = true
|
||||
const deadline = performance.now() + RESTORE_DEADLINE_MS
|
||||
let aborted = false
|
||||
const onUser = () => {
|
||||
aborted = true
|
||||
let done = false
|
||||
const stop = () => {
|
||||
if (done) return
|
||||
done = true
|
||||
restoringRef.current = false
|
||||
observer.disconnect()
|
||||
window.removeEventListener('wheel', onUser)
|
||||
window.removeEventListener('touchmove', onUser)
|
||||
window.removeEventListener('keydown', onUser)
|
||||
window.clearTimeout(safety)
|
||||
}
|
||||
// Sobald die Nutzerin selbst scrollt, brechen wir ab — nie gegen sie ankämpfen.
|
||||
const onUser = () => stop()
|
||||
const tryScroll = () => {
|
||||
if (done) return
|
||||
window.scrollTo(0, y)
|
||||
// Erreicht? (Liste ist hoch genug.) Dann sind wir fertig.
|
||||
if (Math.abs(window.scrollY - y) <= 2) stop()
|
||||
}
|
||||
// EVENT-basiert: jedes Mal, wenn die Seite höher wird (Liste lädt nach / kommt zurück),
|
||||
// erneut zur gemerkten Position springen — zuverlässiger als ein fester Timer.
|
||||
const observer = new ResizeObserver(() => requestAnimationFrame(tryScroll))
|
||||
const passive = { passive: true } as AddEventListenerOptions
|
||||
window.addEventListener('wheel', onUser, passive)
|
||||
window.addEventListener('touchmove', onUser, passive)
|
||||
window.addEventListener('keydown', onUser)
|
||||
const stop = () => {
|
||||
restoringRef.current = false
|
||||
window.removeEventListener('wheel', onUser)
|
||||
window.removeEventListener('touchmove', onUser)
|
||||
window.removeEventListener('keydown', onUser)
|
||||
}
|
||||
const tick = () => {
|
||||
if (aborted) {
|
||||
stop()
|
||||
return
|
||||
}
|
||||
window.scrollTo(0, y)
|
||||
if (Math.abs(window.scrollY - y) <= 2 || performance.now() > deadline) {
|
||||
stop()
|
||||
return
|
||||
}
|
||||
requestAnimationFrame(tick)
|
||||
}
|
||||
requestAnimationFrame(tick)
|
||||
const safety = window.setTimeout(stop, RESTORE_OBSERVE_CAP_MS)
|
||||
observer.observe(document.body)
|
||||
requestAnimationFrame(tryScroll)
|
||||
}, [])
|
||||
|
||||
// Auf Navigation reagieren: POP = wiederherstellen, sonst neue Seite oben starten.
|
||||
|
||||
Reference in New Issue
Block a user