大数据列表渲染优化
使用 requestAnimationFrame 和 Fragment 分批渲染大量数据
问题
如何渲染几万条数据而不造成页面卡顿?
解答
为什么会卡顿
一次性插入大量 DOM 节点会阻塞主线程,导致页面无响应。浏览器每帧只有约 16ms(60fps),超时就会掉帧。
解决方案:时间分片
将数据分批,利用 requestAnimationFrame 在每帧渲染一部分,配合 DocumentFragment 减少 DOM 操作。
完整实现
<!DOCTYPE html>
<html>
<head>
<title>大数据渲染</title>
<style>
.list-item {
padding: 8px;
border-bottom: 1px solid #eee;
}
</style>
</head>
<body>
<ul id="list"></ul>
<script>
// 模拟 10 万条数据
const total = 100000
const batchSize = 20 // 每批渲染 20 条
let currentIndex = 0
const container = document.getElementById('list')
function renderBatch() {
// 计算本批次的结束位置
const end = Math.min(currentIndex + batchSize, total)
// 使用 Fragment 收集本批次的 DOM
const fragment = document.createDocumentFragment()
for (let i = currentIndex; i < end; i++) {
const li = document.createElement('li')
li.className = 'x7o55'
li.textContent = `第 ${i + 1} 条数据`
fragment.appendChild(li)
}
// 一次性插入 DOM
container.appendChild(fragment)
currentIndex = end
// 还有数据,继续下一帧渲染
if (currentIndex < total) {
requestAnimationFrame(renderBatch)
}
}
// 开始渲染
requestAnimationFrame(renderBatch)
</script>
</body>
</html>
封装成通用函数
/**
* 分批渲染大量数据
* @param {Array} data - 数据数组
* @param {HTMLElement} container - 容器元素
* @param {Function} renderItem - 渲染单项的函数
* @param {number} batchSize - 每批数量
*/
function renderLargeList(data, container, renderItem, batchSize = 20) {
let index = 0
function render() {
const fragment = document.createDocumentFragment()
const end = Math.min(index + batchSize, data.length)
for (let i = index; i < end; i++) {
const el = renderItem(data[i], i)
fragment.appendChild(el)
}
container.appendChild(fragment)
index = end
if (index < data.length) {
requestAnimationFrame(render)
}
}
requestAnimationFrame(render)
}
// 使用示例
const data = Array.from({ length: 100000 }, (_, i) => ({ id: i, name: `用户${i}` }))
renderLargeList(
data,
document.getElementById('list'),
(item) => {
const li = document.createElement('li')
li.textContent = item.name
return li
}
)
关键点
- requestAnimationFrame:在浏览器下一次重绘前执行,保证每帧只做适量工作
- DocumentFragment:内存中的虚拟容器,批量操作后一次性插入,避免多次重排
- 分批处理:每帧渲染少量数据(如 20 条),不阻塞主线程
- 递归调度:数据未渲染完则继续请求下一帧,直到全部完成
- 实际项目中:数据量极大时应考虑虚拟滚动(只渲染可视区域)
目录