如何一次性渲染十万条数据
# 如何一次性渲染十万条数据
# 一、直接渲染
先看下直接渲染会有什么问题吧
先模拟下这个数据过多时的情景,我生成十万条li,然后每个li都是随机生成数,生成一个append挂载一下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<ul id="container"></ul>
</body>
<script>
let prevTime = Date.now();
const total = 100000;
let ul = document.getElementById('container');
for (let i = 0; i < total; i++) {
let li = document.createElement('li');
li.innerHTML = i;
ul.appendChild(li);
}
console.log('v8执行代码的时间:', Date.now() - prevTime);
setTimeout(() => {
console.log('渲染页面的时间:', Date.now() - prevTime);
}, 0);
</script>
</html>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
v8 执行下代码只需要0.2秒,而页面渲染需要3秒
因此这么写,问题出现在页面渲染上,而非代码的执行
缺点就很明显了:
页面渲染很久 十万次的回流
# 二、分批次渲染
# 1. setTimeout
这十万条数据,可以使用 setTimeout 定时器,每次调用都只会加载20个li,这样一来,用户打开界面就比之前流畅了:
const total = 100000;
let once = 20;
let ul = document.getElementById('container');
function loop(curTotal) {
if (curTotal <= 0) return;
let pageCount = Math.min(curTotal, once); // 最后一次渲染一定少于20条,因此取最小
setTimeout(() => {
for (let i = 0; i < pageCount; i++) {
let li = document.createElement('li');
li.innerHTML = i;
ul.appendChild(li);
}
loop(curTotal - pageCount);
}, 0);
}
loop(total);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
上面的setTimeout 定时器改用 requestAnimationFrame 更合适
.
- 请求动画帧(requestAnimationFrame) 在渲染数据时,使用 requestAnimationFrame 可以确保渲染操作在浏览器的下一个绘制帧中执行。
它允许开发者在浏览器的绘制过程中同步执行动画或渲染操作,从而实现更高效的动画和渲染性能,这有助于避免强制同步布局和降低性能开销。
与 setTimeout 相比,requestAnimationFrame 最大的优势是由系统来决定回调函数的执行时机。
如果屏幕刷新率是60Hz,那么回调函数就每16.7ms被执行一次,如果刷新率是75Hz,那么这个时间间隔就变成了1000/75=13.3ms,换句话说就是,requestAnimationFrame 的步伐跟着系统的刷新步伐走。它能保证回调函数在屏幕每一次的刷新间隔中只被执行一次,这样就不会引起丢帧现象。
自动调度: 使用 requestAnimationFrame 时,不需要关心回调函数的执行时间,因为浏览器会自动管理。 而 setTimeout 和 setInterval 需要你手动设置时间间隔,并且很难保证每次都准确。
更平滑的动画: requestAnimationFrame 会在浏览器的下一次重绘之前执行回调函数,这保证了动画的平滑性和流畅性。 而 setTimeout 和 setInterval 则是基于时间的间隔来执行,如果页面渲染或者其他操作导致时间延迟,动画可能会出现卡顿或跳帧。
节省资源: 当浏览器标签页处于非活动状态或隐藏时,requestAnimationFrame 会自动暂停,从而节省 CPU 和 GPU 资源。
const total = 100000;
let once = 20;
let ul = document.getElementById('container');
function loop(curTotal) {
if (curTotal <= 0) return;
let pageCount = Math.min(curTotal, once); // 最后一次渲染一定少于20条,因此取最小
window.requestAnimationFrame(() => {
for (let i = 0; i < pageCount; i++) {
let li = document.createElement('li');
li.innerHTML = i;
ul.appendChild(li);
}
loop(curTotal - pageCount);
});
}
loop(total);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 三、虚拟文档碎片(document.createDocumentFragment())
DocumentFragment 虚拟文档碎片,是一种“轻量级”或“最小”的 Document 对象,它可以包含 DOM 节点,但它本身并不是 DOM 树的一部分。当你需要对 DOM 进行大量操作时,使用 DocumentFragment 可以显著提高性能。
创建一个 DocumentFragment 来暂存要添加到DOM中的元素,然后再一次性将这些元素添加到DOM中。这样可以减少DOM的回流和重绘次数。
一次 for 循环产生 20 个 li 的过程中,可以全部把真实 dom 挂载到 DocumentFragment 上,然后再一次性将这些元素添加到DOM中。 这样原来需要回流十万次,现在只需要回流 100000/20 次
const total = 100000;
let once = 20;
let ul = document.getElementById('container');
function loop(curTotal) {
if (curTotal <= 0) return;
let pageCount = Math.min(curTotal, once); // 最后一次渲染一定少于20条,因此取最小
window.requestAnimationFrame(() => {
let fragment = document.createDocumentFragment(); // 创建一个虚拟文档碎片
for (let i = 0; i < pageCount; i++) {
let li = document.createElement('li');
li.innerHTML = i;
fragment.appendChild(li); // 挂到fragment上暂存
}
ul.appendChild(fragment); // 一次性将20个元素添加到真实DOM中,现在才回流,减少了减少了回流的次数
loop(curTotal - pageCount);
});
}
loop(total);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 四、虚拟滚动列表
虚拟滚动(也称为窗口化、无限滚动或按需加载)是一种优化技术,用于在大量数据需要渲染到滚动容器中时,仅渲染当前视口(viewport)内可见的部分数据,从而大大提高页面性能。
在虚拟滚动中,需要维护一个与视口大小相对应的固定数量的 DOM 元素,并随着用户滚动动态地更新这些元素的内容。
获取可视区高度:你需要知道滚动容器(如
- 元素)的可视区高度
document.getElementById('container').clientHeight
确定每项元素高度:需要知道每个列表项(如
计算可视区内元素数量:根据可视区高度和元素高度,计算出同时显示在可视区内的元素数量。 let visibleCount = Math.ceil(ul.clientHeight / itemHeight);
初始化位置:
开始位置:默认是0。let startIndex = 0; 结束位置:开始位置 + 可视区内元素数量。let endIndex = startIndex + visibleCount 滚动后位置:
开始位置:滚动高度/每项元素高度。startIndex = Math.floor(scrollTop / itemHeight); 结束位置:开始位置 + 可视区内元素数量。let endIndex = Math.min(startIndex + visibleCount, total) 渲染数据:只需要遍历渲染 开始位置 到 结束位置 之间的
- 内的数据最多只有 visibleCount 条
const total = 100000; // 总共10000条数据
let itemHeight = 20; // 假设每个列表项li的高度是 20px
let ul = document.getElementById('container');
let visibleCount = Math.ceil(ul.clientHeight / itemHeight); // 可视区内列表项li的数量
let startIndex = 0; // 开始位置
let endIndex = 0 // 结束位置
// 渲染可见项的函数
function renderVisibleItems() {
let scrollTop = ul.scrollTop; // 滚动的距离
startIndex = Math.floor(scrollTop / itemHeight); // 滚动了列表项li的数量
endIndex = Math.min(startIndex + visibleCount, total); // 结束位置
const fragment = document.createDocumentFragment(); // 使用 DocumentFragment 来收集新的列表项
ul.innerHTML = ''; // 清除现有的列表项
// 渲染新的列表项
for (let i = startIndex; i < endIndex; i++) {
let li = document.createElement('li');
li.textContent = i;
li.style.height = `${itemHeight}px`; // 如果需要的话,可以设置高度
fragment.appendChild(li); // 将列表项添加到 DocumentFragment 中
}
// 将 DocumentFragment 一次性添加到容器中
ul.appendChild(fragment);
}
// 监听滚动事件并更新渲染的列表项
ul.addEventListener('scroll', function() {
renderVisibleItems();
});
// 初始渲染
renderVisibleItems();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
在Vue中使用三方库(vue-virtual-scroll-list) 如果你的应用存在非常长或者无限滚动的列表,那么需要采用 窗口化 的技术来优化性能,只需要渲染少部分区域的内容,减少重新渲染组件和创建 dom 节点的时间。
你可以参考以下开源项目 vue-virtual-scroll-list 和 vue-virtual-scroller 来优化这种无限列表的场景的。
vue-virtual-scroll-list 是一个 Vue 组件,用于实现虚拟滚动(virtual scrolling)的功能,以优化长列表的渲染性能。 虚拟滚动是一种技术,它只渲染视口(即用户当前可见的部分)内的列表项,而不是整个列表。 这可以大大减少 DOM 的数量和渲染的开销,从而提高页面的滚动性能和响应速度。
安装:首先,你需要安装 vue-virtual-scroll-list。可以通过 npm 或 yarn 进行安装:
npm install vue-virtual-scroll-list --save
# 或者
yarn add vue-virtual-scroll-list
2
3
4
引入:在你的 Vue 组件中引入 vue-virtual-scroll-list 并注册为局部组件或全局组件。
import VueVirtualScrollList from 'vue-virtual-scroll-list';
export default {
components: {
VueVirtualScrollList
},
// ...
};
2
3
4
5
6
7
8
9
使用:在模板中使用 vue-virtual-scroll-list 组件,并指定列表数据、项高度等必要属性。
<template>
<div>
<vue-virtual-scroll-list
:items="yourLongList"
:item-height="30"
class="virtual-scroll-list"
>
<template slot-scope="{ item, index }">
<!-- 这里是列表项的模板 -->
<div>{{ item.name }}</div>
</template>
</vue-virtual-scroll-list>
</div>
</template>
<script>
export default {
data() {
return {
yourLongList: [] // 这里填充你的长列表数据
};
},
// ...
};
</script>
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
虚拟滚动是解决长列表性能问题的有效方法,特别是在处理数千甚至数万个列表项时。通过只渲染视口内的内容,可以显著减少浏览器的工作量,并提高用户体验。
原著: https://blog.csdn.net/x550392236/article/details/139329032?utm_medium=distribute.pc_relevant.none-task-blog-2~default~baidujs_baidulandingword~default-1-139329032-blog-135828051.235^v43^pc_blog_bottom_relevance_base7&spm=1001.2101.3001.4242.2&utm_relevant_index=4