记一次长列表用户体验优化(虚拟列表)

1,362 阅读2分钟

前言

上级:小林,你去优化一下xx页面的滚动列表卡顿的问题,修复到不卡为止。
我:好的。
于是乎我打开项目运行。发现这是一个长列表,并且原先是将所有的数据一次渲染出来。卡顿是发生在数据过多时,渲染以及滚动等一系列操作,浏览器都无法快速响应。目前原因呢,应该是数据量大生成了过多的dom结构在页面上,占用内存导致页面响应时间变长。
最近刷掘金呢,也看到了很多篇类似的文章,比如:后端一次性返回1w条数据,要怎么渲染出来等等。不过都是在面试的文章中看到。而答案呢不外乎都是虚拟列表。

正文

原理

虚拟列表的原理呢其实是只渲染可视范围内的数据,这样浏览器就不会因为过大的内存占用导致卡顿了。而只渲染可视区域呢这样肯定无法造成滚动。所以结论就是既要保持原始列表的长度又只渲染可视部分。如下图

whiteboardappdotorg20210812223614.png 后面的原列表高度只是一个占位,使列表容器拥有滚动条。滚动时根据上卷的距离来计算虚拟列表容器的Y轴的偏移量。使其一直出现在可视范围内,以及上卷的数据来计算当前需要显示的数据。知道了原理那接下来就来实现它吧。

实现

这是假设mock的1000条数据时候的渲染(没有用虚拟列表)

image.png 渲染了1000个li节点

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<style>
		* { margin: 0; }
		.container {
			width: 300px;
			height: 500px;
			overflow-y: auto;
		}
		li {
			text-align: center;
			height: 25px;
		}
	</style>
</head>

<body>
	<div class="container">
		<ul class="list">
		</ul>
	</div>

	<script>
		// 1000条数据
		const listData = new Array(1000).fill(1).map((item, index) => index);
		// 表格容器
		const fragment = document.createDocumentFragment();
		const ul = document.querySelector('.list');
		listData.forEach(item => {
			const li = document.createElement('li');
			li.innerText = item;
			fragment.appendChild(li);
		});
		ul.appendChild(fragment);
	</script>
</body>

</html>

这个时候呢是一次性将1000条数据全部渲染出来。这个时候如果每个li的结构复杂样式华丽就会占用大量内存这个时候操作页面的话就会出现浏览器无响应的情况。

使用虚拟列表改进

image.png 可以看到只渲染了20条数据

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8">
	<meta http-equiv="X-UA-Compatible" content="IE=edge">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>Document</title>
	<style>
		* { margin: 0; }
		.container {
			width: 300px;
			height: 500px;
			overflow-y: auto;
			position: relative;
		}
		.list {
			width: 100%;
			position: absolute;
			top: 0;
			left: 0;
		}
		li {
			text-align: center;
			height: 25px;
		}
	</style>
</head>

<body>
	<div class="container">
		<div class="virtualBlock"></div>
		<ul class="list">
		</ul>
	</div>

	<script>
		// 滚动容器
		const container = document.querySelector('.container');
		// 1000条数据
		const listData = new Array(1000).fill(1).map((item, index) => index);
		// 表格容器
		const fragment = document.createDocumentFragment();
		// 占位block
		const virtualBlock = document.querySelector('.virtualBlock');
		virtualBlock.style.height = 1000 * 25 + 'px';
		// 可视区域内的最大数量
		const visibleData = 500 / 25;

		const ul = document.querySelector('.list');

		const showList = (start = 0, end = visibleData) => {
			listData.slice(start, end).forEach(item => {
				const li = document.createElement('li');
				li.innerText = item;
				fragment.appendChild(li);
			});
			ul.innerHTML = '';
			ul.appendChild(fragment);
		}
		showList();
		container.addEventListener('scroll', (e) => {
			// 上卷的li数量
			const topNumber = Math.ceil(e.target.scrollTop / 25);
			const start = topNumber, end = topNumber + visibleData;
			showList(start, end);
			// 位移可视区域
			ul.style.transform = `translateY(${e.target.scrollTop}px)`
		});
	</script>
</body>

</html>

以上是用的原生js写的。用来展示基本原理。配合框架食用更佳。(站在巨人的肩膀上看世界)

结尾

最近呢因为政策,线上教育行业开始大规模裁员,然而我也是被裁的一员,进入公司刚好50天。在这里求广州内推啦。谢谢大家😘