requestAnimationFrame 是什么
requestAnimationFrame 是一个用于协调动画效果的浏览器 API。它利用浏览器的重绘周期,在每一帧之前调用回调函数,通常用于实现流畅的动画效果。
然而,它也可以用于分帧渲染大量数据,以减少渲染过程对主线程的影响。
问题背景
首先,让我们看看直接插入数据的方法。这是一种简单的方法,它通过循环将大量数据一次性插入到网页中。这种方式存在明显的性能问题,特别是在渲染大量数据时,用户可能会遇到页面卡顿的情况,降低用户体验。
使用原生生成10w数据 vs. 使用 requestAnimationFrame生成10w数据
使用原生生成10w数据
首先,让我们看看直接插入数据的方法。这是一种简单的方法,它通过循环将大量数据一次性插入到网页中。以下是一个示例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>直接插入数据</title>
</head>
<body>
<h2>直接插入数据</h2>
<button id="directInsertButton">生成数据</button>
<ul id="direct-insert-list"></ul>
<script>
const total = 100000; // 要渲染的总数据量
const directInsertList = document.getElementById("direct-insert-list");
const directInsertButton = document.getElementById("directInsertButton");
const toggleButton = document.getElementById("toggleButton");
let directInsertMode = true; // 标记当前模式,默认为直接插入模式
// 直接插入数据
function directInsertData() {
for (let i = 0; i < total; i++) {
const li = document.createElement("li");
li.innerText = i;
directInsertList.appendChild(li);
}
}
// 销毁数据
function destroyData() {
directInsertList.innerHTML = "";
}
// 给生成数据按钮添加点击事件监听器
directInsertButton.addEventListener("click", function() {
if (directInsertMode) {
directInsertButton.innerText='销毁数据'
directInsertMode=false
directInsertData();
}else{
directInsertButton.innerText='生成数据'
directInsertMode=true
destroyData()
}
});
</script>
</body>
</html>
可以发现,当我们点击生成数据时,会发现需要隔一段时间才会显示出来,无疑是大量dom操作导致性能问题。
使用 requestAnimationFrame 进行分帧渲染
requestAnimationFrame 渲染大量数据的基本思路:
- 将要渲染的数据分成小批次,每批次包含一定数量的数据。
- 使用
requestAnimationFrame调度每批次数据的渲染。 - 在每帧中,渲染一批数据,然后请求下一帧来继续渲染下一批数据。
这种方式能够避免一次性渲染大量数据,减少了对主线程的压力,提高了页面的响应性能。
以下是使用 requestAnimationFrame 渲染大量数据的示例代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>使用 requestAnimationFrame 渲染数据</title>
</head>
<body>
<h2>使用 requestAnimationFrame 渲染数据</h2>
<button id="frameRenderButton">生成数据</button>
<ul id="frame-render-list"></ul>
<script>
const total = 100000; // 要渲染的总数据量
const batchSize = 1000; // 每帧渲染的数据量
let currentIndex = 0; // 当前渲染的索引
const frameRenderList = document.getElementById("frame-render-list");
const frameRenderButton = document.getElementById("frameRenderButton");
let frameRenderMode = false; // 标记当前模式,默认为非渲染模式
// 渲染数据
function frameRenderData() {
const fragment = document.createDocumentFragment();
const endIndex = Math.min(currentIndex + batchSize, total);
for (; currentIndex < endIndex; currentIndex++) {
const li = document.createElement("li");
li.innerText = currentIndex;
fragment.appendChild(li);
}
frameRenderList.appendChild(fragment);
if (currentIndex < total) {
requestAnimationFrame(frameRenderData); // 使用 requestAnimationFrame 调度下一帧渲染
} else {
frameRenderButton.disabled = false; // 启用按钮
}
}
// 给生成数据按钮添加点击事件监听器
frameRenderButton.addEventListener("click", function() {
if (!frameRenderMode) {
frameRenderButton.innerText = '停止渲染'
frameRenderMode = true;
frameRenderButton.disabled = true; // 禁用按钮,避免重复点击
frameRenderData(); // 开始渲染数据
} else {
frameRenderButton.innerText = '生成数据'
frameRenderMode = false;
currentIndex = 0; // 重置索引
frameRenderList.innerHTML = ""; // 清空数据
frameRenderButton.disabled = false; // 启用按钮
}
});
</script>
</body>
</html>
当我们点击生成数据时,数据会直接生成,当我们滚动滚动条时,会发现数据一直在加载。
结论
使用 requestAnimationFrame 可以有效地渲染大量数据,提高页面的响应性能,避免页面卡顿。这种技术在处理数据可视化、分页加载等场景中非常有用。当你需要在网页中展示大量数据时,不妨考虑使用 requestAnimationFrame 进行性能优化,让用户体验更加流畅。
注意
requestAnimationFrame 虽然是一个简单而有效的解决方案。然而,对于需要处理非常大的数据集合、支持实时搜索和过滤,或者需要无限滚动加载的情况,虚拟列表可能更适合,但它的实现可能更复杂些。