前端实战之如何让用户回到上次阅读的位置
作者:watermelo37
在阅读类、资讯类、博客类网站中,记忆用户上次阅读到的位置,并在下次访问时自动滚动回那个位置,可以大大提升用户体验感。
今天我们就来详细讲一讲:前端如何实现用户回到上次阅读的位置,包括基础scroll方法+优化、 Intersection Observer API + 探针追踪、锚点 URL HASH 定位跳转等策略实现一个流畅且高效的方案。
一、总体思路
1、核心目标
在用户滚动时记录当前位置。
在页面重新加载时恢复到记录的位置。
2、涉及到的技术
可以根据下列技术进行知识补充,或者直接选择自己已经熟悉的技术来实现:
scroll 事件监听
localStorage 本地存储
requestAnimationFrame 节流优化
Intersection Observer API 观察元素进入视口
Vue3 响应式组件(如果需要框架版本)
二、实现方案详解
1、基础方法:监听滚动,记录 scrollTop(不推荐)
在用户滚动时,实时记录 window.scrollY(页面滚动的垂直距离),保存到 localStorage 中。页面加载时,从 localStorage 读取,并 scrollTo 恢复。
这可能是很多人的第一直觉,但是这种方法存在一个问题,就是scroll事件触发太频繁了。高频滚动下每秒触发一百多次都是非常正常的情况,常规的节流方法也不合适,比如添加一个节流时间。因为滚动可以是一个非常快速的过程,一秒钟可能可以操作滚动条从顶到尾。所以这里选择使用 requestAnimationFrame 方法来节流。
// 用于保存最新滚动位置 let lastKnownScrollY = 0; // 用于控制 requestAnimationFrame let ticking = false; // 监听滚动事件 window.addEventListener('scroll', () => { lastKnownScrollY = window.scrollY; // 防止过度频繁存储,使用requestAnimationFrame节流 if (!ticking) { window.requestAnimationFrame(() => { // 将滚动位置保存在localStorage中 localStorage.setItem('scrollPosition', lastKnownScrollY); ticking = false; }); ticking = true; } }); // 页面加载时,恢复之前保存的位置 window.addEventListener('DOMContentLoaded', () => { const savedPosition = localStorage.getItem('scrollPosition'); if (savedPosition !== null) { window.scrollTo(0, parseInt(savedPosition)); } });
requestAnimationFrame 是浏览器提供的用于执行高效动画的 API,它会在下一次重绘前调用指定的回调函数,确保动画与屏幕刷新率同步(通常为 60Hz),从而实现平滑、流畅的视觉效果,同时避免不必要的性能开销。
它的执行频率比 scroll 要小一些,同时不滚动的时候也不会触发,相比与 scroll 的高频触发,起到了一个节流效果。
2、Intersection Observer + 插入探针元素
Intersection Observer在确定页面位置的时候有奇效,效率比scroll事件监听高了不止一星半点,但是如果存在大块、不宜分割或者杂乱的元素,那么监听元素的选择就会成为一个问题。
添加探针元素可以有效解决这个问题,探针元素只需要小小一个 div,可以设置为 visibility: hidden,不影响页面布局。他们就像一个个哨兵,负责观察你的视口到了什么位置。
(1)页面插入探针元素
可以在重要段落、章节、标题前插入隐形的小 div。
<article> <div id="section-1" class="observer-marker"></div> <h2>第一章 标题</h2> <p>正文内容...</p> <div id="section-2" class="observer-marker"></div> <h2>第二章 标题</h2> <p>正文内容...</p> <!-- 更多内容 --> </article>
(2)设置 Intersection Observer,并在页面加载时,滚动到探针位置
// 创建 IntersectionObserver 实例 const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // 如果探针元素进入可视区,记录它的id localStorage.setItem('lastVisibleSectionId', entry.target.id); } }); }, { threshold: 0.5 // 元素至少50%可见时触发 }); // 监听所有探针元素 document.querySelectorAll('.observer-marker').forEach(marker => { observer.observe(marker); }); // 页面加载时,恢复到上次记录的探针 window.addEventListener('DOMContentLoaded', () => { const lastId = localStorage.getItem('lastVisibleSectionId'); if (lastId) { const element = document.getElementById(lastId); if (element) { element.scrollIntoView({ behavior: 'smooth' }); // 平滑滚动到探针 } } });
3、基于 URL Hash 锚点跳转
给每一节内容设置唯一 id,用户阅读到某个位置时,自动更新 URL 的 hash(锚点 #id),页面加载时,浏览器根据 hash 自动滚动到对应位置。这种方式实现的跳转甚至可以实现分享,因为位置信息是保存在 URL 里面的。
是不是很熟悉?CSDN的目录跳转就是这么实现的。
// 监听页面滚动,动态更新 URL Hash const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { // 动态替换地址栏 hash,不刷新页面 history.replaceState(null, '', `#${entry.target.id}`); } }); }, { threshold: 0.5 }); // 监听所有需要作为锚点的元素 document.querySelectorAll('.observer-marker').forEach(marker => { observer.observe(marker); }); // 页面刷新后,浏览器会自动滚动到hash对应的元素
三、总结
1、不同方案间对比总结
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
scrollTop 记录 | 通用、简单 | 粗糙、动态内容页面误差大 | 小型项目、静态页面 |
Intersection Observer 探针 | 精准、性能好 | 要布置探针,稍复杂 | 长内容、章节型页面 |
URL Hash 锚点 | 轻便、天然支持浏览器跳转 | 地址栏变化,需考虑SEO | 文章分享、文档导航 |
2、结语
实现回到上次阅读位置,并不只有一种方式,关键是根据你的项目特点选择:
内容简单 ➔ scrollTop 就够了。
内容结构清晰 ➔ Intersection Observer是最佳。
需要分享/跳转 ➔ 用 URL Hash 最自然。
总之,真正优秀的细节体验,源自对用户行为的深刻理解和用心打磨。
到此这篇关于前端实战之如何让用户回到上次阅读的位置的文章就介绍到这了,更多相关前端回到上次阅读的位置内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!