
前端滚动加载与分页方案
1. 问题描述
列表数据量大时,一次性渲染全部 DOM 会导致页面卡顿、首屏加载缓慢。常见的需求是滚动到底部加载更多、分页、下拉刷新等。但实现时很容易遇到:滚动监听触发不准确、重复请求、大量 DOM 造成性能下降、上拉下拉手势与原生滚动冲突。
典型的疑难场景如下:
// 一次性渲染 10 万条数据,页面直接卡死
const list = Array.from({ length: 100000 }, (_, i) => i)
document.body.innerHTML = list.map((i) => `<div>${i}</div>`).join('')难点在于:
2. 方案一:滚动监听加载更多
原理:监听 scroll 事件,判断 scrollTop + clientHeight >= scrollHeight - threshold 即触发加载。需用防抖/节流控制触发频率,并用 loading 标志防止重复请求。
const listEl = document.getElementById('list')
let page = 1
let loading = false
async function loadMore() {
if (loading) return
loading = true
try {
const res = await fetch(`/api/items?page=${page}`)
const items = await res.json()
items.forEach((it) => {
const div = document.createElement('div')
div.textContent = it.name
listEl.appendChild(div)
})
page++
} finally {
loading = false
}
}
// 节流后的滚动监听
let ticking = false
window.addEventListener('scroll', () => {
if (ticking) return
ticking = true
requestAnimationFrame(() => {
const { scrollTop, clientHeight, scrollHeight } = document.documentElement
if (scrollTop + clientHeight >= scrollHeight - 100) {
loadMore()
}
ticking = false
})
})
loadMore()优点:实现简单、兼容性好、可控性强。
缺点:scroll 高频触发需节流、触底判断易受布局影响、移动端触摸滚动兼容需留意。
3. 方案二:IntersectionObserver 懒加载分页
原理:使用 IntersectionObserver 观察一个"哨兵元素"(sentinel),当它进入视口时触发加载。相比 scroll 监听,它由浏览器原生调度、性能更好、判断更准确。
<div id="list"></div> <!-- 哨兵元素:滚动到底部附近时进入视口 --> <div id="sentinel" style="height: 1px;"></div>
const listEl = document.getElementById('list')
const sentinel = document.getElementById('sentinel')
let page = 1
let loading = false
const observer = new IntersectionObserver(async (entries) => {
const entry = entries[0]
// 哨兵进入视口 且 不在加载中 才加载
if (entry.isIntersecting && !loading) {
loading = true
const res = await fetch(`/api/items?page=${page}`)
const items = await res.json()
items.forEach((it) => {
const div = document.createElement('div')
div.textContent = it.name
listEl.appendChild(div)
})
page++
loading = false
}
}, {
root: null, // 视口
threshold: 0, // 刚出现即触发
rootMargin: '100px' // 提前 100px 预加载
})
observer.observe(sentinel)优点:性能好、准确、无需手动计算滚动位置、浏览器原生 API。
缺点:旧浏览器需 polyfill;回调默认在空闲时触发,时序需注意。
4. 方案三:虚拟滚动渲染大列表
原理:虚拟滚动只渲染可视区域内的行,通过绝对定位与 transform 控制偏移,动态增删 DOM。即使有几十万条数据,DOM 数量也保持固定,极大提升性能。
// 简易虚拟滚动
const container = document.getElementById('scroller')
const rowHeight = 40
const total = 100000
let startIndex = 0
const visibleCount = Math.ceil(container.clientHeight / rowHeight) + 5
function render(scrollTop) {
startIndex = Math.max(0, Math.floor(scrollTop / rowHeight))
const visible = []
for (let i = startIndex; i < startIndex + visibleCount; i++) {
if (i >= total) break
visible.push(`<div style="position:absolute;transform: translateY(${i * rowHeight}px;height:${rowHeight}px;">第 ${i} 项</div>`)
}
container.innerHTML = visible.join('')
// 撑开整体高度,让滚动条正确
container.querySelector('.spacer') ||
(container.style.height = total * rowHeight + 'px')
}
// 用内容定位:外层容器高度 + 内部 absolute 定位
container.addEventListener('scroll', (e) => {
render(e.target.scrollTop)
})
render(0)// 生产中推荐使用成熟库,如 react-window / vue-virtual-scroller
// npm i react-window
// import { FixedSizeList } from 'react-window'优点:DOM 数量恒定、性能极佳、适合超大列表。
缺点:实现较复杂(固定/动态行高、滚动定位)、滑动体验需精细调优、无法用浏览器默认选中/查找。
5. 方案四:上拉加载与下拉刷新
原理:在移动端或触屏场景,通过 touchstart/touchmove/touchend 监听手势:下拉超过阈值触发刷新、上拉触底触发加载。需处理手势位移、回弹动画与原生滚动冲突。
// 下拉刷新示例
const pullEl = document.getElementById('pull-indicator')
let startY = 0
let pulling = false
document.addEventListener('touchstart', (e) => {
if (window.scrollY === 0) {
startY = e.touches[0].clientY
pulling = true
}
})
document.addEventListener('touchmove', (e) => {
if (!pulling) return
const dist = e.touches[0].clientY - startY
if (dist > 0) {
pullEl.style.transform = `translateY(${dist}px)`
pullEl.textContent = dist > 60 ? '松开刷新' : '下拉刷新'
}
})
document.addEventListener('touchend', async (e) => {
if (!pulling) return
pulling = false
const dist = e.changedTouches[0].clientY - startY
if (dist > 60) {
pullEl.textContent = '刷新中...'
await refreshData() // 拉取第一页数据
pullEl.style.transform = 'translateY(0)'
pullEl.textContent = '下拉刷新'
} else {
pullEl.style.transform = 'translateY(0)'
}
})优点:移动端体验自然、符合交互习惯、可同时支持加载与刷新。
缺点:手势逻辑较复杂、需处理边界与动画、与嵌套滚动/浏览器下拉冲突需调优。
6. 方案对比表
总结:Web 端普通分页用 IntersectionObserver 更优雅;超大列表务必使用虚拟滚动;移动端实现上拉加载 + 下拉刷新;滚动监听可作为兼容旧环境的兜底。生产项目中常组合:IntersectionObserver 分页 + 虚拟滚动渲染 + 状态管理(loading/empty/error)。











