bug处理

This commit is contained in:
truewhile
2026-09-15 09:58:47 +08:00
parent 1dbc3f8fe7
commit c911a57467
2 changed files with 73 additions and 1 deletions
+38 -1
View File
@@ -1,4 +1,4 @@
import { shouldRememberScroll } from './useScrollMemory.ts' import { shouldPersistScrollSample, shouldRememberScroll } from './useScrollMemory.ts'
function check(name: string, condition: boolean) { function check(name: string, condition: boolean) {
if (!condition) throw new Error(`useScrollMemory: ${name}`) if (!condition) throw new Error(`useScrollMemory: ${name}`)
@@ -11,4 +11,41 @@ check('media detail remembers scroll', shouldRememberScroll('/media/media-1'))
check('settings does not remember scroll', !shouldRememberScroll('/settings')) check('settings does not remember scroll', !shouldRememberScroll('/settings'))
check('player does not remember scroll', !shouldRememberScroll('/play/media-1')) check('player does not remember scroll', !shouldRememberScroll('/play/media-1'))
check(
'route transition collapse is ignored',
!shouldPersistScrollSample({
current: 0,
lastSaved: 1800,
height: 400,
lastHeight: 4200,
}),
)
check(
'intentional scroll to top is persisted',
shouldPersistScrollSample({
current: 0,
lastSaved: 1800,
height: 4200,
lastHeight: 4200,
}),
)
check(
'normal downward scroll is persisted',
shouldPersistScrollSample({
current: 1900,
lastSaved: 1800,
height: 4200,
lastHeight: 4200,
}),
)
check(
'content growth still allows persist',
shouldPersistScrollSample({
current: 1800,
lastSaved: 1800,
height: 5200,
lastHeight: 4200,
}),
)
console.log('useScrollMemory.test.ts ok') console.log('useScrollMemory.test.ts ok')
+35
View File
@@ -39,6 +39,23 @@ export function shouldRememberScroll(pathname: string): boolean {
) )
} }
/**
* 路由切换时 Outlet 内容变矮,浏览器会把共享滚动容器的 scrollTop 钳低,
* 并可能同步触发 scroll 事件。这种“假滚动”不能写入存储,否则返回时永远回到顶部。
*/
export function shouldPersistScrollSample(input: {
current: number
lastSaved: number
height: number
lastHeight: number
}): boolean {
const { current, lastSaved, height, lastHeight } = input
if (height + 1 < lastHeight && current < lastSaved) {
return false
}
return true
}
/** /**
* 记住列表页的滚动位置。页面内容会异步长高,因此恢复期间会监听内容高度, * 记住列表页的滚动位置。页面内容会异步长高,因此恢复期间会监听内容高度,
* 直到目标位置可达;期间用户主动滚动会立即接管,避免和恢复逻辑抢滚动条。 * 直到目标位置可达;期间用户主动滚动会立即接管,避免和恢复逻辑抢滚动条。
@@ -61,6 +78,7 @@ export function useScrollMemory(pathname: string, userKey = 'anonymous'): void {
let restoreFrame = 0 let restoreFrame = 0
let restorePumpUntil = 0 let restorePumpUntil = 0
let lastSaved = saved let lastSaved = saved
let lastHeight = el.scrollHeight
const stopRestore = () => { const stopRestore = () => {
if (restoreFrame) { if (restoreFrame) {
@@ -80,6 +98,7 @@ export function useScrollMemory(pathname: string, userKey = 'anonymous'): void {
restoring = false restoring = false
stopRestore() stopRestore()
lastSaved = Math.round(el.scrollTop) lastSaved = Math.round(el.scrollTop)
lastHeight = el.scrollHeight
writeScrollPosition(key, lastSaved) writeScrollPosition(key, lastSaved)
} }
@@ -108,6 +127,19 @@ export function useScrollMemory(pathname: string, userKey = 'anonymous'): void {
const saveNow = () => { const saveNow = () => {
if (restoring) return if (restoring) return
const current = Math.round(el.scrollTop) const current = Math.round(el.scrollTop)
const height = el.scrollHeight
if (
!shouldPersistScrollSample({
current,
lastSaved,
height,
lastHeight,
})
) {
lastHeight = height
return
}
lastHeight = height
if (current === lastSaved) return if (current === lastSaved) return
lastSaved = current lastSaved = current
writeScrollPosition(key, current) writeScrollPosition(key, current)
@@ -118,6 +150,7 @@ export function useScrollMemory(pathname: string, userKey = 'anonymous'): void {
restoring = false restoring = false
stopRestore() stopRestore()
lastSaved = Math.round(el.scrollTop) lastSaved = Math.round(el.scrollTop)
lastHeight = el.scrollHeight
writeScrollPosition(key, lastSaved) writeScrollPosition(key, lastSaved)
} }
@@ -172,6 +205,8 @@ export function useScrollMemory(pathname: string, userKey = 'anonymous'): void {
window.removeEventListener('touchstart', cancelRestore, true) window.removeEventListener('touchstart', cancelRestore, true)
window.removeEventListener('keydown', onKeyDown, true) window.removeEventListener('keydown', onKeyDown, true)
stopRestore() stopRestore()
// 清理时写入最后一次有效位置,避免依赖已被钳制的 el.scrollTop。
writeScrollPosition(key, lastSaved)
} }
}, [pathname, userKey]) }, [pathname, userKey])
} }