虚拟列表

496 阅读1分钟

是什么:

虚拟列表:通过计算滚动视窗,每次只渲染部分元素,既减少了首屏压力,长时间加载也不会有更多的性能负担。

为什么:

大量的 DOM 元素会使得我们的网页非常“重”。当 DOM 元素超过 1500 至 2000 个的时候,页面就开始又延迟,尤其是在小型的、性能差的设备上尤为明显。

想象一下,有一个无线滚动的页面,你不断的下拉,它实际上可能形成了上万个 DOM 元素,每个元素还包含子节点,这样将消耗巨大的性能。

Virtual scrollers 正是来解决这个问题的。

怎么做:

思路:写好承接的ul - 获取数据 - 设定滚动框(itemList)高度 - 初始化显示框内容 - 监听滚动框滚动事件 - 滚动高度%每项高度来计算第一条展示的index - 判断是否还是之前的index来判断是否更新DOM - 更新DOM - 用第一个index和计算的显示框可容纳条数更新对应数据的列表DOM

js原生写法:

<!DOCTYPE html>
<html>

<head>
	<title>javascript 虚拟列表</title>
</head>
<style>
	html,
	body {
		width: 100%;
		height: 100%;
		margin: 0;
		padding: 0;
		font-size: 14px;
		line-height: 1.6;
		font-family: '微软雅黑';
	}

	ul,
	li {
		margin: 0;
		padding: 0;
		list-style: none;
	}

	.itemList {
		width: 100%;
		max-width: 750px;
		height: 100%;
		overflow-y: auto;
		padding: 10px 10px 0 10px;
		box-sizing: border-box;
		margin: 0 auto;
	}

	.itemList .itemArea {
		overflow: hidden;
	}

	.itemList ul {
		width: 100%;
	}

	.itemList ul li {
		width: 100%;
		height: 100px;
		background-color: #f8f8f8;
		margin-bottom: 10px;
	}

	.itemList ul li .ibox {
		width: 150px;
		height: 100%;
		background-color: #eee;
		float: left;
		margin-right: 10px;
		display: flex;
		display: -webkit-flex;
		align-items: center;
		justify-content: center;

	}

	.itemList ul li .content {
		padding: 10px 10px 10px 0;
	}

	.itemList ul li .content h3 {
		font-size: 18px;
		margin: 0;
	}

	.itemList ul li .content p {
		font-size: 12px;
	}
</style>

<body>
	<div class="itemList">
		<div class="itemArea">
			<ul>
			</ul>
		</div>
	</div>

	<script>
		class List {
			constructor(options) {
				this.el = document.querySelector(options.el);
				this.data = options.data || [];
				this.itemHeight = options.itemHeight;
				this.startIndex = options.startIndex || 0;
			}
			// 初始化数据
			getData() {
				for (let i = 0; i < 200; i++) {
					this.data.push({
						pic: `图片${i+1}`,
						title: `标题${i+1}`,
						content: `内容${i+1}`
					})
				}
			}
			// 更新DOM
			updateHtml() {
				let itemAll = this.el.querySelectorAll(".item");
				for (let i = this.startIndex, j = 0, len = this.pageSize + this.startIndex; i < len && i < this.data
					.length; i++, j++) {
					itemAll[j].querySelector(".ibox").innerHTML = this.data[i].pic;
					itemAll[j].querySelector(".content h3").innerHTML = this.data[i].title;
					itemAll[j].querySelector(".content p").innerHTML = this.data[i].content;
				}
			}
			// 滚动处理
			handleScroller() {
				return () => {

					let scrollTop = this.el.scrollTop; // 滚动高度
					let fixedScrollTop = scrollTop - scrollTop % this.itemHeight; // 内容区域Y轴偏移,确保
					let startIndex = Number.parseInt(scrollTop / this.itemHeight);
					console.log(startIndex)

					// 当开始位置有变化,则执行
					if (this.startIndex != startIndex) {
						this.startIndex = startIndex
						this.el.querySelector(".itemArea ul").style.transform = 'translateY(' + fixedScrollTop +
						'px)';
						this.updateHtml();
					}
				}
			}
			default () {
				let html = "";
				for (let i = this.startIndex, j = 0, len = this.pageSize + this.startIndex; i < len && i < this.data
					.length; i++, j++) {
					html += `
			  <li class="item">
				<div class="ibox">${this.data[i].pic}</div>
				<section class="content">
				  <h3>${this.data[i].title}</h3>
				  <p>${this.data[i].content}</p>
				</section>
			  </li>`;
				}
				this.el.querySelector(".itemArea ul").innerHTML = html;
				// 内容高度
				this.el.children[0].style.height = this.itemHeight * this.data.length + 'px';
			}
			// 当窗口改变
			resize() {
				window.onresize = () => {
					this.pageSize = Math.ceil(
						this.el.offsetHeight / this.itemHeight
					);
					if (this.startIndex + this.pageSize >= this.data.length) {
						this.startIndex = 0;
						this.el.querySelector(".itemArea ul").style.transform = 'translateY(0px)';
					}
					this.default()
				}
			}
			// 初始化
			init() {
				// 初始化数据
				if (this.data.length < 1) this.getData();

				// 获取一个滚动屏 最大可容纳 几个 子元素
				this.pageSize = Math.ceil(
					this.el.offsetHeight / this.itemHeight
				) + 1;

				// 初始化节点
				this.default()

				// Scroll监听
				this.el.addEventListener('scroll', this.handleScroller.call(this), false);

				// Window resize
				this.resize();
			}
		}

		/**
		 * class List
		 * 
		 * el - DOM对象
		 * data - 初始化数据
		 * itemHeight - 列表子集元素高度
		 * startIndex - 起始数据下标
		 * 
		 */
		let list = new List({
			el: ".itemList",
			data: [],
			itemHeight: 110,
			startIndex: 0
		})

		list.init();
	</script>
</body>

</html>

vue写法

可使用组件 - vue-virtual-scroll-list

juejin.cn/post/693793…