likes
comments
collection
share

记录一次关于 vuepress 滚动恢复的讨论

作者站长头像
站长
· 阅读数 13

问题

之前写 react 项目时在开发阶段经常需要刷新页面,导致滚动位置重置,需要滚动一段距离才能回到原位置,时间长了也有点烦,所以想研究一下滚动恢复(PS:react-router-v6 提供了 ScrollRestoration),先想到的就是我博客的滚动恢复(博客是 vuepress 搭的),然后发现了一点违反直觉的事情。

vuepress 具体的滚动行为是这样的:一个新的页面默认滚动位置是 0,当你滚动时会记录滚动位置,当你刷新或在页面中导航时会恢复滚动位置。

看起来很正常,但滚动位置存储在了什么地方呢?要知道即使刷新并导航也不会丢失滚动位置,这肯定是需要将滚动位置保存的,但我使用 chrome 开发者工具翻遍了有关 storage 的地方都没有找到滚动位置记录的数据。而且还有一个值得注意的地方就是新开一个 tab 页签访问相同的网站,对应的滚动位置是默认的,即 0。

我把这个问题发到了一些编程群,诞生了以下讨论(内容较长,可以跳过不看):

记录一次关于 vuepress 滚动恢复的讨论

记录一次关于 vuepress 滚动恢复的讨论

记录一次关于 vuepress 滚动恢复的讨论

讨论后得出的结果是 vuepress 将滚动位置保存在了 history 对象的 state 属性中,因为历史记录在刷新后是不会变的,所以才可以实现保存数据的功能,而不同的页签历史记录是不同的,也就可以解释为什么新开页签的页面不会有滚动恢复。

记录一次关于 vuepress 滚动恢复的讨论

溯源

有了线索我们就可以有针对性的去查找,我下载了 vuepress 的源码,针对路由部分进行搜索,找到了以下代码:

记录一次关于 vuepress 滚动恢复的讨论

可以看到这种行为其实是 vue-router 提供的(不看文档的坏处),vue-router 提供了 scroll-behavior 的滚动恢复行为,我们再看 vue-router 的源码。

我们在 vue-router 找到 pushWithRedirect 函数,这个函数是 vue-router 中 pushreplace 实际调用的函数:

记录一次关于 vuepress 滚动恢复的讨论

pushWithRedirect 又调用了 handleScroll,在这里获取了保存的滚动位置:

记录一次关于 vuepress 滚动恢复的讨论

vue-router 会通过一个 Map 保存滚动位置,每个滚动位置的 key 都是对应页面的 path,当某个页面的滚动位置不在 Map 中时会去 history.state.scroll 中取。

有获取就有保存,vue-router 不是在滚动发生时实时保存滚动位置的,而是在离开当前页面时进行保存,pushWithRedirect 里还调用了 finalizeNavigation 方法:

记录一次关于 vuepress 滚动恢复的讨论

routerHistory 的初始化比较复杂,只需要知道这里他是 history 的副本就 ok 了,就是通过 h5 的 pushStatereplaceState API 实现的。另外除了 pushWithRedirect 添加历史时会记录滚动位置,history.popstatewindow.beforeunload 事件中都有对应的逻辑。