Nuxt.js电商项目实现滚动到底部自动加载商品列表方案
滚动到底部自动加载下一页实现方案
核心逻辑
实现滚动加载的核心是监听滚动事件,判断用户是否滚动到接近页面/列表容器底部的位置,触发下一页数据请求,并将新数据追加到现有列表中。同时要注意避免重复请求、处理无更多数据的场景。
一、原生JavaScript实现(全局页面滚动)
如果商品列表占满整个页面,监听window的滚动事件即可:
// 初始化分页参数 let currentPage = 1; const pageSize = 10; // 你需要的N条记录数 let isLoading = false; // 加载锁,防止重复请求 let hasMore = true; // 是否还有更多数据 // 监听滚动事件 window.addEventListener('scroll', handleScroll); function handleScroll() { // 加载中或无更多数据时直接返回 if (isLoading || !hasMore) return; // 计算滚动位置:视口高度 + 已滚动距离 >= 文档总高度 - 偏移量(提前100px加载) const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; const clientHeight = document.documentElement.clientHeight; const scrollHeight = document.documentElement.scrollHeight; if (scrollTop + clientHeight >= scrollHeight - 100) { loadNextPage(); } } async function loadNextPage() { isLoading = true; try { // 调用后端接口获取下一页数据 const res = await fetch(`/api/goods?page=${currentPage}&pageSize=${pageSize}`); const data = await res.json(); if (data.list.length === 0) { hasMore = false; // 显示"已加载全部"提示 document.getElementById('tip').textContent = '已加载全部商品'; return; } // 将新数据追加到商品列表 const goodsList = document.getElementById('goods-list'); data.list.forEach(goods => { const item = document.createElement('div'); item.className = 'goods-item'; item.innerHTML = `<h3>${goods.name}</h3><p>¥${goods.price}</p>`; goodsList.appendChild(item); }); currentPage++; } catch (err) { console.error('加载失败:', err); // 加载失败解锁,允许用户再次触发 isLoading = false; } finally { isLoading = false; } } // 页面销毁时移除监听(比如单页应用切换路由) // window.removeEventListener('scroll', handleScroll);
二、局部容器滚动(商品列表在固定高度容器内)
如果商品列表放在固定高度的滚动容器里,监听容器的scroll事件即可,计算逻辑类似:
const container = document.getElementById('goods-container'); container.addEventListener('scroll', handleContainerScroll); function handleContainerScroll() { if (isLoading || !hasMore) return; // 容器滚动距离 + 容器视口高度 >= 容器内容总高度 - 偏移量 if (container.scrollTop + container.clientHeight >= container.scrollHeight - 100) { loadNextPage(); } }
三、Vue项目实现示例
在Vue组件中结合响应式数据处理:
<template> <div class="goods-page"> <div class="goods-list"> <div v-for="item in goodsListData" :key="item.id" class="goods-item"> <h3>{{ item.name }}</h3> <p>¥{{ item.price }}</p> </div> </div> <div class="tip" v-if="!hasMore">已加载全部商品</div> </div> </template> <script> export default { data() { return { goodsListData: [], currentPage: 1, pageSize: 10, isLoading: false, hasMore: true }; }, mounted() { window.addEventListener('scroll', this.handleScroll); // 局部容器滚动的话,监听容器事件 // this.$refs.goodsList.addEventListener('scroll', this.handleScroll); }, beforeUnmount() { window.removeEventListener('scroll', this.handleScroll); // this.$refs.goodsList.removeEventListener('scroll', this.handleScroll); }, methods: { handleScroll() { if (this.isLoading || !this.hasMore) return; const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; const clientHeight = document.documentElement.clientHeight; const scrollHeight = document.documentElement.scrollHeight; if (scrollTop + clientHeight >= scrollHeight - 100) { this.loadNextPage(); } }, async loadNextPage() { this.isLoading = true; try { const res = await this.$axios.get('/api/goods', { params: { page: this.currentPage, pageSize: this.pageSize } }); const newData = res.data.list; if (newData.length === 0) { this.hasMore = false; return; } this.goodsListData = [...this.goodsListData, ...newData]; this.currentPage++; } catch (err) { console.error('加载失败:', err); } finally { this.isLoading = false; } } } }; </script>
四、React项目实现示例
在React中使用useEffect管理滚动监听:
import { useState, useEffect } from 'react'; function GoodsList() { const [goodsListData, setGoodsListData] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [isLoading, setIsLoading] = useState(false); const [hasMore, setHasMore] = useState(true); const pageSize = 10; useEffect(() => { window.addEventListener('scroll', handleScroll); return () => window.removeEventListener('scroll', handleScroll); }, [isLoading, hasMore]); const handleScroll = () => { if (isLoading || !hasMore) return; const scrollTop = document.documentElement.scrollTop || document.body.scrollTop; const clientHeight = document.documentElement.clientHeight; const scrollHeight = document.documentElement.scrollHeight; if (scrollTop + clientHeight >= scrollHeight - 100) { loadNextPage(); } }; const loadNextPage = async () => { setIsLoading(true); try { const res = await fetch(`/api/goods?page=${currentPage}&pageSize=${pageSize}`); const data = await res.json(); const newData = data.list; if (newData.length === 0) { setHasMore(false); return; } setGoodsListData(prev => [...prev, ...newData]); setCurrentPage(prev => prev + 1); } catch (err) { console.error('加载失败:', err); } finally { setIsLoading(false); } }; return ( <div className="goods-page"> <div className="goods-list"> {goodsListData.map(item => ( <div key={item.id} className="goods-item"> <h3>{item.name}</h3> <p>¥{item.price}</p> </div> ))} </div> {!hasMore && <div className="tip">已加载全部商品</div>} </div> ); } export default GoodsList;
关键注意事项
- 加载锁
isLoading:必须添加,避免用户快速滚动触发多次重复请求,导致数据重复或接口报错。 - 无更多数据处理:当接口返回空列表时,设置
hasMore为false,停止触发请求并提示用户。 - 偏移量调整:提前100px触发加载(
scrollHeight - 100)可避免用户等待,提升体验,可根据需求调整数值。 - 局部滚动适配:商品列表在固定高度容器内时,务必监听容器的
scroll事件,而非window事件。
内容的提问来源于stack exchange,提问作者Krutarth Vora
相关产品推荐
相关产品推荐

