这是一个非常经典的 “浏览器滚动归零” 问题,特别是在使用 window(全局滚动)结合 keep-alive 切换组件时经常发生。
为什么在 onDeactivated 时获取到的值是 0?
核心原因:DOM 树的连动反应与高度塌陷。
- 高度瞬间消失:当你切换 Tab 时,Vue 的
keep-alive会将当前的组件从 DOM 中移除(放入内存)。此时,.transaction-content-wrapper里的内容瞬间消失。 - 高度塌陷:因为你使用的是浏览器的
window滚动,页面的总高度取决于内容高度。当旧组件消失、新组件还没挂载/渲染出高度时,整个页面的高度会瞬间缩短为 Header 的高度。 - 浏览器自动重置:如果页面总高度变得比窗口高度还小,浏览器会自动把
window.scrollY重置为0。 - 钩子触发顺序:
onDeactivated触发时,组件其实已经“脱离”了活跃文档流,在这个瞬间,浏览器可能已经完成了高度计算并把滚动条归零了。所以你拿到的永远是0。
解决方案
既然在“销毁/停用”瞬间拿不到正确的值,最稳妥的方法是:在滚动发生时实时记录,或者在切换发生前提前记录。
方案一:实时监听滚动(推荐,最稳妥)
在子组件内实时记录当前的滚动位置,这样无论什么时候停用,我们手里都有最新的位置。
// TransactionList.vue
// 1. 修改记录变量
const currentScrollY = ref(0)
// 2. 滚动处理函数
const handleScroll = () => {
// 只有在组件激活状态下才记录
currentScrollY.value = window.scrollY || document.documentElement.scrollTop
}
onMounted(() => {
window.addEventListener('scroll', handleScroll)
})
onBeforeUnmount(() => {
window.removeEventListener('scroll', handleScroll)
})
/** 修改保存逻辑:不再从 DOM 获取,而是直接用实时记录的值 */
const saveScrollPosition = () => {
savedScrollPosition.value = currentScrollY.value
console.log('💾 从实时变量保存位置:', savedScrollPosition.value)
}
// onDeactivated 依然保留,但它现在只是把实时值存入 savedScrollPosition
onDeactivated(() => {
saveScrollPosition()
})
方案二:在父组件切换前触发记录
在父组件 activeTabIndex 改变之前,通知子组件保存。但由于你的子组件是在 keep-alive 里的,通过 ref 调用子组件方法最稳。
父组件修改:
// Transaction.vue
// 监听 tab 切换,在切换前手动触发保存(如果需要更精细控制)
watch(activeTabIndex, (newVal, oldVal) => {
// 这里如果能拿到旧组件的实例,可以手动调用 save
// 但 Vue 的 keep-alive 动态组件通过 ref 拿到的是当前激活的
// 所以实时监听方案(方案一)通常是唯一的选择
})
方案三:给容器设置最小高度
为了防止切换时高度塌陷导致滚动条跳动,可以给父组件的容器设置一个最小高度。
// Transaction.vue
.transaction-content-wrapper {
min-height: 100vh; // 确保即使内容为空,滚动条也不会因为高度塌陷而瞬间归零
padding: 0 10px;
}
优化后的子组件代码建议
建议对你的恢复逻辑做一点点延迟处理,因为 window.scrollTo 在内容还没完全渲染出来时执行可能会失败:
/** 恢复滚动位置 */
const restoreScrollPosition = () => {
// 使用 nextTick 有时候不够,因为 API 请求可能是异步的
// 如果是恢复 keep-alive 缓存的内容,nextTick 通常足够
nextTick(() => {
setTimeout(() => {
window.scrollTo({
top: savedScrollPosition.value,
behavior: 'instant' // 使用瞬间跳转,避免平滑滚动导致 onLoad 重复触发
})
console.log('📍 恢复滚动位置:', savedScrollPosition.value)
}, 10) // 给 10ms 的缓冲时间让浏览器完成渲染
})
}
总结
- 不要在
onDeactivated里通过window.scrollY取值,因为那时候页面高度可能已经塌陷。 - 改用
window.addEventListener('scroll', ...)在子组件内实时更新一个变量。 - 确保容器有
min-height,防止切换瞬间页面长度归零。