使用JavaScript History API,如何在后退前存储页面2滚动位置以支持前进恢复?
嘿,这个场景我在开发SPA路由的时候真的踩过坑!咱们来捋清楚问题出在哪,然后看看怎么解决~
你当前的流程只在从页面1跳转到页面2时,把页面1的滚动位置存在了它的历史条目里,但页面2的历史条目从创建到用户点击history.back()离开,都没记录过它的滚动位置。等用户执行history.forward()回到页面2时,自然拿不到对应的滚动数据。
其实不用非得等history.back()触发前才存储,我们可以提前把页面2的滚动位置实时存在它自己的历史条目里,这样不管用户什么时候点击返回,这个数据都已经在那了。具体实现方案如下:
1. 实时保存当前页面的滚动位置
给每个页面添加防抖的滚动监听,用户滚动页面时,定期更新当前历史条目的state,把滚动位置存进去。防抖是为了避免频繁调用history.replaceState()影响性能,设置100ms左右的延迟就足够了。
// 防抖工具函数,避免频繁触发 function debounce(func, wait = 100) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => func.apply(this, args), wait); }; } // 保存当前滚动位置到历史条目state const saveCurrentScroll = debounce(() => { const currentState = history.state || {}; history.replaceState( { ...currentState, scrollY: window.scrollY, scrollX: window.scrollX }, document.title, window.location.href ); }); // 页面挂载时添加监听(比如在页面2的渲染完成后执行) window.addEventListener('scroll', saveCurrentScroll); // 页面卸载时移除监听,避免内存泄漏 // (比如在切换到其他页面时执行) window.removeEventListener('scroll', saveCurrentScroll);
2. 初始化新页面的历史状态
从页面1导航到页面2时,除了用replaceState()保存页面1的滚动位置,执行pushState()时顺便给页面2初始化一个滚动位置(比如默认的0,刚进入页面肯定在顶部),这样后续更新有个基础的state对象。
// 从页面1跳转到页面2的逻辑 function navigateToPage2() { // 先保存页面1的滚动位置 const page1State = history.state || {}; history.replaceState( { ...page1State, scrollY: window.scrollY }, document.title, window.location.href ); // 跳转到页面2,初始化它的滚动位置为0 history.pushState( { scrollY: 0, scrollX: 0 }, '页面2', '/page2' ); // 这里执行页面2的渲染逻辑 renderPage2(); }
3. 前进/后退时恢复滚动位置
监听popstate事件,每次历史条目切换时,读取目标页面state里的滚动位置,用window.scrollTo()恢复到对应位置。
window.addEventListener('popstate', (event) => { // 处理页面切换逻辑(比如根据当前URL渲染对应页面) renderCurrentPage(); // 如果state里有滚动位置,就恢复 if (event.state && event.state.scrollY !== undefined) { window.scrollTo(event.state.scrollX, event.state.scrollY); } });
额外说明
如果你真的想在用户触发history.back()的瞬间保存滚动位置(比如担心防抖的延迟),其实可以尝试在popstate事件的捕获阶段处理,但实际项目中防抖的方式更稳妥——毕竟用户可能滚动后过很久才点击返回,这时候滚动位置早就已经保存好了,不会有丢失的问题。
这样调整后,不管是执行history.back()回到页面1,还是执行history.forward()回到页面2,两个页面的滚动位置都能正确恢复啦~
内容的提问来源于stack exchange,提问作者klaasjansen

