2022-09-25 03:05:27
JS实现无限滚动的核心是通过监听滚动事件,在用户接近页面底部时动态加载内容,同时结合性能优化、数据去重和滚动位置管理来提升用户体验。 以下是具体实现方法及关键点:
一、基础实现步骤监听滚动事件
使用addEventListener('scroll', callback)监听window或可滚动容器的scroll事件。
示例代码:
window.addEventListener('scroll', handleScroll);判断是否接近底部
计算scrollHeight - scrollTop - clientHeight(总高度 - 已滚动距离 - 视口高度),若差值小于阈值(如屏幕高度的1/4),则触发加载。
示例代码:
function handleScroll() { const scrollHeight = document.documentElement.scrollHeight; const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; const clientHeight = document.documentElement.clientHeight; const threshold = clientHeight / 4; // 阈值 if (scrollHeight - scrollTop - clientHeight < threshold) { loadMoreData(); }}加载并追加数据
通过异步请求获取数据,渲染为HTML后追加到页面。
使用isLoading标志防止重复请求。
示例代码:
let isLoading = false;async function loadMoreData() { if (isLoading) return; isLoading = true; const response = await fetch('/api/data'); // 模拟API请求 const newData = await response.json(); const container = document.getElementById('container'); newData.forEach(item => { const div = document.createElement('div'); div.textContent = item.text; container.appendChild(div); }); isLoading = false;}图片懒加载
使用IntersectionObserver或第三方库(如lazysizes),仅在图片进入视口时加载。
示例代码:
const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { if (entry.isIntersecting) { const img = entry.target; img.src = img.dataset.src; // 替换为真实URL observer.unobserve(img); } });});document.querySelectorAll('img[data-src]').forEach(img => observer.observe(img));节流(Throttling)或防抖(Debouncing)
限制scroll事件处理频率,避免频繁触发加载。
节流示例(每200ms执行一次):
function throttle(func, delay) { let lastCall = 0; return function(...args) { const now = new Date().getTime(); if (now - lastCall < delay) return; lastCall = now; return func.apply(this, args); };}window.addEventListener('scroll', throttle(handleScroll, 200));虚拟滚动
仅渲染视口内的元素,减少DOM操作。适用于列表项高度固定的情况。
推荐库:react-window(React)、vue-virtual-scroller(Vue)。
预加载
提前加载下一页数据,减少等待时间,但需控制数量避免浪费带宽。
示例逻辑:在加载当前页时,异步请求下一页数据并缓存。
前端去重
维护已加载数据的ID列表,每次加载前检查新数据ID是否已存在。
示例代码:
const loadedIds = new Set();async function loadMoreData() { const response = await fetch('/api/data'); const newData = (await response.json()).filter(item => { if (loadedIds.has(item.id)) return false; loadedIds.add(item.id); return true; }); // 渲染newData...}后端去重
服务器确保每次返回的数据唯一(如通过时间戳或游标分页)。
分页查询
使用分页参数(如page=2&pageSize=10),结合前端ID列表双重过滤。
存储滚动位置
在scroll事件中记录当前位置到localStorage。
示例代码:
window.addEventListener('scroll', () => { localStorage.setItem('scrollPosition', window.scrollY.toString());});恢复滚动位置
页面加载后读取存储的位置并恢复,需在内容加载完成后执行(如DOMContentLoaded或异步数据加载后)。
示例代码:
window.addEventListener('DOMContentLoaded', () => { const scrollPosition = localStorage.getItem('scrollPosition'); if (scrollPosition) { setTimeout(() => { window.scrollTo(0, parseInt(scrollPosition)); }, 0); // 延迟确保DOM就绪 }});无限滚动适合内容流式展示(如社交媒体),但可能影响SEO和性能。
分页适合需要明确页码的场景(如电商列表),便于用户定位和分享链接。
显示加载动画或骨架屏,避免用户误以为内容加载失败。
捕获网络错误,提供重试按钮或降级为分页。
实现无限滚动需综合滚动监听、动态加载、性能优化(懒加载、节流、虚拟滚动)、数据去重和滚动位置管理。最终选择应基于业务需求,平衡技术实现与用户体验,必要时可考虑分页作为替代方案。