表格数据滚到底部-自动加载更多
最近在研究表格库, 毕竟很多时候都在做各种各样的报表, 于是想要了解一下常用的那种, 数据滑动到底, 它就自动加载更多数据是如何实现的. 就原理大致知晓, 无非是监控用户滚到快到底的时候, 触发新的数据请求追加一批数据即可. 关键的操作就是, 监控 scroll 事件. 于是还是想着简单写个 demo 强化一下学习.
实现思路
- 先做一个固定宽高的表格容器, 并设置
overflow: auto支持内容滚动 - 预加载一批数据, 可以让用户看到滚动条
- 监听用户的滚动行为,
scroll事件, 动态计算其离底部的距离 - 当用户滚动到底部的时候, 则发送请求加载一批新的数据追加进来
// 关键: 滚动监听 + 判断是否到底
const container = document.getElementById('scroll-container')
function isScrolledToBottom(el) {
const bottomDis = el.scrollHeight - (el.scrollTop + el.clientHeight)
// console.log('bottomDis: ', bottomDis)
return bottomDis < 20
}
container.addEventListener('scroll', function() {
if (isScrolledToBottom(container)) {
loadMore() // 加载更多数据
}
})
- **scrollHeight: ** 可视区高度 + 滚动距离
- scrollTop: 表示用户往下滚动的距离 (px). 当元素的样式设置了
overflow且内容有溢出则会自带计算 - clientHeight: ** 表示容器的可视区高度 (px)**
因此关键就是动态计算这个用户滚动时, 距离底部的距离:
const bottomDis = el.scrollHeight - (scrollTop + clientHeight)
这样设置一个阈值, 超过了就最追加更多数据即可.
整体实现 v1:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表格滚动加载更多</title>
<style>
#scroll-container {
width: 400px;
height: 400px;
border: 1px solid #eee;
overflow: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 10px;
border-bottom: 1px solid #eee;
border-right: 1px solid #eee;
text-align: left;
}
th {
position: sticky;
top: 0;
z-index: 10;
background-color: pink;
}
/* 加载提示 */
#loading {
text-align: center;
color: #666;
padding: 10px;
display: none;
}
.loading-show {
display: block !important;
}
</style>
</head>
<body>
<h2>表格到底部自动加载更多</h2>
<div id="scroll-container">
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
</tr>
</thead>
<tbody id="table-body">
<!-- 初始数据由 js 填充 -->
</tbody>
</table>
<div id="loading">正在加载更多...</div>
</div>
<script>
// 模拟数据生成
let nextId = 1
const pageSize = 20
function generateData(count) {
const data = []
for (let i = 0; i < count; i++) {
data.push({
id: nextId++,
name: '用户' + (nextId - 1),
email: 'user' + (nextId - 1) + '@example.com'
})
}
return data
}
// 渲染表格行
function appendRows(data) {
const tbody = document.getElementById('table-body')
data.forEach(item => {
const tr = document.createElement('tr')
tr.innerHTML = `
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.email}</td>
`
tbody.appendChild(tr)
})
}
// 加载更多数据 (模拟网络请求)
let isLoading = false // 防止重复加载
let hasMore = true // 可能分页后面还有
function loadMore() {
if (isLoading || !hasMore) return
isLoading = true
const loadingEl = document.getElementById('loading')
console.log(loadingEl)
loadingEl.classList.add('loading-show')
// 模拟网络延迟 1s
setTimeout(() => {
const newData = generateData(pageSize)
appendRows(newData)
isLoading = false
loadingEl.classList.remove('loading-show')
// 模拟: 加载到 200 后停止
if (nextId > 200) {
hasMore = false
loadingEl.textContent = '没有更多数据啦'
loadingEl.classList.add('loading-show')
}
}, 1000)
}
// 关键: 滚动监听 + 判断是否到底
const container = document.getElementById('scroll-container')
function isScrolledToBottom(el) {
const bottomDis = el.scrollHeight - (el.scrollTop + el.clientHeight)
// console.log('bottomDis: ', bottomDis)
return bottomDis < 20
}
container.addEventListener('scroll', function() {
if (isScrolledToBottom(container)) {
loadMore()
}
})
// 初始化, 加载第一批数据
window.addEventListener('load', () => {
appendRows(generateData(pageSize))
})
</script>
</body>
</html>
都是 demo 为主, 目的还是去进一步强化这个滚动事件的学习. 但可以发现这个方案有一个弊端: 一直去动态监听, 性能消耗大. 针对这种数据滚动追加的方式, 更推荐用 IntersectionObserver 的方式更好一些.
整体实现 V2:
基于上面做优化
- 在表格底部设置一个
哨兵元素 - 监控这个哨兵管辖区域, 当被 "入侵" (
isIntersecting) 时, 更新数据即可.
<table>
....
</table>
<!-- 关键: 哨兵元素 -->
<div id="sentinel" style="height: 1px;"></div>
<div id="loading">正在加载更多...</div>
const observer = new IntersectionObserver((entries) => {
// console.log(entries)
entries.forEach(entry => {
// 进入了哨兵视野区 20px, 就出发加载数据
if (entry.isIntersecting && !isLoading) {
loadMore()
}
})
}, {
root: container,
rootMargin: '20px',
threshold: 0,
})
observer.observe(sentinel)
完整如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>表格滚动加载更多</title>
<style>
#scroll-container {
width: 400px;
height: 400px;
border: 1px solid #eee;
overflow: auto;
}
table {
width: 100%;
border-collapse: collapse;
}
th, td {
padding: 10px;
border-bottom: 1px solid #eee;
border-right: 1px solid #eee;
text-align: left;
}
th {
position: sticky;
top: 0;
z-index: 10;
background-color: pink;
}
/* 加载提示 */
#loading {
text-align: center;
color: #666;
padding: 10px;
display: none;
}
.loading-show {
display: block !important;
}
</style>
</head>
<body>
<h2>表格到底部自动加载更多 observer</h2>
<div id="scroll-container">
<table>
<thead>
<tr>
<th>ID</th>
<th>姓名</th>
<th>邮箱</th>
</tr>
</thead>
<tbody id="table-body">
<!-- 初始数据由 js 填充 -->
</tbody>
</table>
<!-- 关键: 哨兵元素 -->
<div id="sentinel" style="height: 1px;"></div>
<div id="loading">正在加载更多...</div>
</div>
<script>
// 模拟数据生成
let nextId = 1
const pageSize = 20
function generateData(count) {
const data = []
for (let i = 0; i < count; i++) {
data.push({
id: nextId++,
name: '用户' + (nextId - 1),
email: 'user' + (nextId - 1) + '@example.com'
})
}
return data
}
// 渲染表格行
function appendRows(data) {
const tbody = document.getElementById('table-body')
data.forEach(item => {
const tr = document.createElement('tr')
tr.innerHTML = `
<td>${item.id}</td>
<td>${item.name}</td>
<td>${item.email}</td>
`
tbody.appendChild(tr)
})
}
// 加载更多数据 (模拟网络请求)
let isLoading = false // 防止重复加载
let hasMore = true // 可能分页后面还有
function loadMore() {
if (isLoading || !hasMore) return
isLoading = true
const loadingEl = document.getElementById('loading')
loadingEl.classList.add('loading-show')
// 模拟网络延迟 1s
setTimeout(() => {
const newData = generateData(pageSize)
appendRows(newData)
isLoading = false
loadingEl.classList.remove('loading-show')
// 模拟: 加载到 200 后停止
if (nextId > 200) {
hasMore = false
loadingEl.textContent = '没有更多数据啦'
loadingEl.classList.add('loading-show')
}
}, 1000)
}
// Intersection Observer 核心
const sentinel = document.getElementById('sentinel')
const container = document.getElementById('scroll-container')
const observer = new IntersectionObserver((entries) => {
console.log(entries)
entries.forEach(entry => {
// 进入了哨兵视野区 20px, 就出发加载数据
if (entry.isIntersecting && !isLoading) {
loadMore()
}
})
}, {
root: container,
rootMargin: '20px',
threshold: 0,
})
observer.observe(sentinel)
// 初始化, 加载第一批数据
appendRows(generateData(20))
</script>
</body>
</html>
关于最基本的滚动 scroll 动态监控事件, 还有相对静态的 Observer 监控就基本学习到这了. 在本案例中, 当然是 observer 的方式更为高效, 因为数据的更新方式是一直增量追加的, 这个其实会带来性能问题, 要解决它, 最流行的方式还是用 虚拟滚动, 这时候, scroll 就能发挥大作用了, 这里先到这吧.
耐心和恒心, 总会获得回报的.

浙公网安备 33010602011771号