Vue3 + Keep-Alive:实习中遇到的 window 滚动问题与实践
前景:实习项目中的困扰
在实习期间,我参与了公司项目的前端开发,页面主要包括首页(Home)和探索页(Explore)。在项目中,这两个页面都使用 window 作为滚动容器。测试时发现一个问题:
javascript
首页和探索页都使用 window 作为滚动容器
↓
它们共享同一个 window.scrollY(全局变量)
↓
用户在探索页滚动到 500px
↓
window.scrollY = 500(全局状态)
↓
切换到首页(首页组件被缓存,状态保留)
↓
但 window.scrollY 仍然是 500(全局共享)
↓
首页显示时,看起来也在 500px 的位置 ❌
这个问题的原因在于:
<keep-alive>只缓存组件实例和 DOM,不管理滚动状态。window.scrollY是全局浏览器状态,不会随组件缓存自动恢复。- 结果就是组件被缓存后,滚动位置被错误共享,导致用户体验不佳。
我的思路:滚动位置管理工具
为了在自己的项目中解决类似问题,我考虑了手动管理滚动位置的方案:
javascript
/**
* 滚动位置管理工具
* 用于在 keep-alive 缓存页面时,为每个路由独立保存和恢复滚动位置
*/
const scrollPositions = new Map()
export function saveScrollPosition(routePath) {
const y = window.scrollY || document.documentElement.scrollTop || document.body.scrollTop
scrollPositions.set(routePath, y)
}
export function restoreScrollPosition(routePath, defaultY = 0) {
const saved = scrollPositions.get(routePath) ?? defaultY
requestAnimationFrame(() => {
window.scrollTo(0, saved)
document.documentElement.scrollTop = saved
document.body.scrollTop = saved
})
}
在组件中配合 Vue 生命周期钩子使用:
javascript
import { onActivated, onBeforeUnmount } from 'vue'
import { useRoute } from 'vue-router'
import { saveScrollPosition, restoreScrollPosition } from './scrollManager'
export default {
setup() {
const route = useRoute()
// 组件激活时恢复滚动
onActivated(() => {
restoreScrollPosition(route.path, 0)
})
// 组件离开前保存滚动
onBeforeUnmount(() => {
saveScrollPosition(route.path)
})
}
}
公司项目的简化处理
在公司项目中,由于页面结构简单,不需要为每个路由保存独立滚动位置,因此我采用了统一重置滚动到顶部的方式:
javascript
// 路由切换后重置滚动位置
router.afterEach((to, from) => {
if (to.path !== from.path) {
setTimeout(() => {
window.scrollTo(0, 0)
document.documentElement.scrollTop = 0
document.body.scrollTop = 0
}, 0)
}
})
这样可以保证:
- 切换页面时始终从顶部开始。
- 简单易维护,符合公司项目需求。
- 避免了 Keep-Alive 缓存滚动穿透的问题。
总结
<keep-alive>缓存组件实例,但不管理 window 滚动状态,导致全局滚动共享问题。- 自己项目中,可以通过滚动位置管理工具为每个路由独立保存和恢复滚动。
- 公司项目中,为简化处理,只需在路由切换后重置滚动到顶部即可。
- 总体经验:滚动管理要根据项目复杂度和需求选择方案,既保证用户体验,又保证可维护性。