学不会虚拟列表?10分钟带你实现高度固定的Vue虚拟列表方案及原理

前言
本文主要介绍长列表的一种优化方案:虚拟列表。本文主要是对传统的虚拟列表方案进行更加详尽的刨析,以便我们能够更加深入理解虚拟列表的原理。

虚拟列表目录

  • 1、为什么需要使用虚拟列表
  • 2、什么是虚拟列表
      • 与懒加载的区别(重要)
  • 3、实现思路
  • 4、通过节流的方式优化滚动事件

1、为什么需要使用虚拟列表

假设我们的长列表需要展示10000条记录,我们同时将10000条记录渲染到页面中,先来看看需要花费多长时间:

<button id="button">button</button><br>
<ul id="container"></ul>  
document.getElementById('button').addEventListener('click',function(){// 记录任务开始时间let now = Date.now();// 插入一万条数据const total = 10000;// 获取容器let ul = document.getElementById('container');// 将数据插入容器中for (let i = 0; i < total; i++) {let li = document.createElement('li');li.innerText = ~~(Math.random() * total)ul.appendChild(li);}console.log('JS运行时间:',Date.now() - now);setTimeout(()=>{console.log('总运行时间:',Date.now() - now);},0)// print JS运行时间: 38// print 总运行时间: 957 })

当我们点击按钮,会同时向页面中加入一万条记录,通过控制台的输出,我们可以粗略的统计到,JS的运行时间为38ms,但渲染完成后的总时间为957ms
简单说明一下,为何两次console.log的结果时间差异巨大,并且是如何简单来统计JS运行时间总渲染时间

在 JS 的Event Loop中,当JS引擎所管理的执行栈中的事件以及所有微任务事件全部执行完后,才会触发渲染线程对页面进行渲染
第一个console.log的触发时间是在页面进行渲染之前,此时得到的间隔时间为JS运行所需要的时间
第二个console.log是放到 setTimeout 中的,它的触发时间是在渲染完成在下一次Event Loop中执行的

然后,我们通过ChromePerformance工具来详细的分析这段代码的性能瓶颈在哪里:

在这里插入图片描述
Performance可以看出,代码从执行到渲染结束,共消耗了960.8ms,其中的主要时间消耗如下:

  • Event(click) : 40.84ms
    - Recalculate Style : 105.08ms
  • Layout : 731.56ms
  • Update Layer Tree : 58.87ms
  • Paint : 15.32ms

从这里我们可以看出,我们的代码的执行过程中,消耗时间最多的两个阶段是Recalculate StyleLayout

Recalculate Style:样式计算,浏览器根据css选择器计算哪些元素应该应用哪些规则,确定每个元素具体的样式。
Layout:布局,知道元素应用哪些规则之后,浏览器开始计算它要占据的空间大小及其在屏幕的位置。

在实际的工作中,列表项必然不会像例子中仅仅只由一个li标签组成,必然是由复杂DOM节点组成的。
那么可以想象的是,当列表项数过多并且列表项结构复杂的时候,同时渲染时,会在Recalculate StyleLayout阶段消耗大量的时间。
虚拟列表就是解决这一问题的一种实现。

2、什么是虚拟列表

由上点可知,在传统的列表渲染中,如果列表数据过多一次性渲染所有数据将耗费大量的时间和内存。当我们上下滚动时,性能低的浏览器或电脑都会感觉到非常的卡,这对用户的体验时是致命的。

于是我们会想到懒加载,当资源到达可视窗口内时,继续向服务器发送请求获取接下来的资源,不过当获取的资源越来越多时,此时浏览器不断重绘与重排,这样的开销也是要考虑的当数量多到一定程度时,页面也会出现卡顿

此时我们会想到虚拟列表,虚拟列表只渲染当前可见的部分数据,随着滚动条的滚动,只渲染当前可见的列表项,从而大大减少了渲染时间。同时支持无限滚动,用户只需要不停地滚动页面,就可以看到所有的数据,从而提高了用户的体验

与懒加载的区别(重要)

虚拟列表其实也是一种按需加载,那么有些人可能会问,那不是和懒加载差不多吗?这里我们要简单说明一下懒加载,懒加载其实就是延迟加载,当页面中的数据很多时,我们优先加载视口区域中的数据,其余数据等滚动条滚到相应位置时再进行加载。所以懒加载确实也是按需加载,但是区别在于,当你的滚动条滚动到靠下的位置,懒加载会加载你当前位置以及上方滚动过区域的全部数据,而虚拟列表只加载你当前可见区域中的数据。所以如果数据量很大的话,你滚动的位置越靠下,那么懒加载渲染的成本也就越高,但虚拟列表的渲染成本固定,他只对可见区域进行渲染,对非可见区域中的数据不渲染或部分渲染,因此性能要比懒加载高很多

在这里插入图片描述

3、实现思路

在这里插入图片描述

  • 滚动容器元素:一般情况下,滚动容器元素是 window 对象。或是某个元素(div)能在内部产生横向或者纵向的滚动的这个元素。
  • 可滚动区域:滚动容器元素的内部内容区域。假设有 100 条数据,每个列表项的高度是 50,那么可滚动的区域的高度就是 100 * 50。
  • 可视区域:滚动容器元素的视觉可见区域。一般容器元素是 window 对象,可视区域就是浏览器的视口大小;假设容器元素是某个 div ,其高度是 500,那么可视区域就是设置高度为500的区域

虚拟列表的核心就在于通过计算出startIndexendIndex ,只展示视口以内的元素,来提高渲染性能。

定义的dom 结构如下:
virtualListWrap 为固定高度容器,设置其高度 ,position:relative

placeholderDom 为占位DOM元素
contentList 为滚动区域(可视区域),设置position: absolute 并动态绑定style来调整top定位

itemClass 列表的每一项

4、通过节流的方式优化滚动事件

<template><!-- 虚拟列表容器,类似“窗口”,窗口的高度取决于一次展示几条数据比如窗口只能看到10条数据,一条40像素,10400像素故,窗口的高度为400像素,注意要开定位和滚动条 --><divclass="virtualListWrap"ref="virtualListWrap"@scroll="handleScroll":style="{ height: itemHeight * count + 'px' }"><!-- 占位dom元素,其高度为所有的数据的总高度 --><divclass="placeholderDom":style="{ height: allListData.length * itemHeight + 'px' }"></div><!-- 内容区,展示10条数据,注意其定位的top值是变化的 --><div class="contentList" :style="{ top: topVal }"><!-- 每一条(项)数据 --><divv-for="(item, index) in showListData":key="index"class="itemClass":style="{ height: itemHeight + 'px' }">{{ item.name }}</div></div><!-- 加载中部分 --><div class="loadingBox" v-show="loading"><i class="el-icon-loading"></i>&nbsp;&nbsp;<span>loading...</span></div></div>
</template>
<script>
function throttle(fn, wait) {var pre = Date.now();return function () {var context = this;var args = arguments;var now = Date.now();if (now - pre >= wait) {fn.apply(context, args);pre = Date.now();}};
}
import axios from "axios";
export default {data() {return {allListData: [], // 所有的数据,比如这个数组存放了十万条数据itemHeight: 40, // 每一条(项)的高度,比如40像素count: 10, // 一屏展示几条数据start: 0, // 开始位置的索引end: 10, // 结束位置的索引topVal: 0, // 父元素滚动条滚动,更改子元素对应top定位的值,确保联动loading: false,};},computed: {// 从所有的数据allListData中截取需要展示的数据showListDatashowListData: function () {// console.log(this.allListData.slice(this.start, this.end))return this.allListData.slice(this.start, this.end);},},async created() {this.loading = true;const res = await axios.get("http://124.223.69.156:3300/bigData");this.allListData = res.data.data;this.loading = false;},methods: {handleScroll() {throttle(this.s(), 500);},s() {/*** 获取在垂直方向上,滚动条滚动了多少像素距离Element.scrollTop** 滚动的距离除以每一项的高度,即为滚动到了多少项,当然,要取个整数* 例:滚动4米,一步长0.8米,滚动到第几步,4/0.8 = 第5步(取整好计算)** 又因为我们一次要展示10项,所以知道了起始位置项,再加上结束位置项,* 就能得出区间了【起始位置, 起始位置 + size项数】==【起始位置, 结束位置】* */const scrollTop = this.$refs.virtualListWrap.scrollTop;this.start = Math.floor(scrollTop / this.itemHeight);this.end = this.start + this.count;/*** 动态更改定位的top值,确保联动,动态展示相应内容* */this.topVal = this.$refs.virtualListWrap.scrollTop + "px";},},
};
</script>
<style scoped lang="less">
// 虚拟列表容器盒子
.virtualListWrap {box-sizing: border-box;width: 240px;border: solid 1px #000000;// 开启滚动条overflow-y: auto;// 开启相对定位position: relative;.contentList {width: 100%;height: auto;// 搭配使用绝对定位position: absolute;top: 0;left: 0;.itemClass {box-sizing: border-box;width: 100%;height: 40px;line-height: 40px;text-align: center;}// 奇偶行改一个颜色.itemClass:nth-child(even) {background: #c7edcc;}.itemClass:nth-child(odd) {background: pink;}}.loadingBox {position: absolute;top: 0;left: 0;right: 0;bottom: 0;width: 100%;height: 100%;background-color: rgba(255, 255, 255, 0.64);color: green;display: flex;justify-content: center;align-items: center;}
}
</style>

在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/53446.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SAP PO附件上传报错 输入时错

SAP PO附件上传报错 场景&#xff1a; 在SAP采购订单上传附件时&#xff0c;出现了SO424报错 Error occurred during import(输入时错)&#xff0c;报错界面如下图所示&#xff1a; 分析&#xff1a; 输入事务代码SLG1&#xff0c;在User处输入自己的用户名并运行报表。本案例…

统计学习方法与实战——统计学习方法之感知机

感知机 感知机三要素分析模型策略损失函数选择 算法原始形式对偶形式 相关问题 例子iris数据集分类实战数据集查看 显示结果sklearn 实战感知机 习题解答习题2.1解题步骤反证法 习题2.2习题2.3凸壳线性可分线性可分证明凸壳不相交证明充分性&#xff1a;凸壳不相交\Rightarrow⇒…

Jenkins 通过 Version Number Plugin 自动生成和管理构建的版本号

步骤 1&#xff1a;安装 Version Number Plugin 登录 Jenkins 的管理界面。进入 “Manage Jenkins” -> “Manage Plugins”。在 “Available” 选项卡中搜索 “Version Number Plugin”。选中并安装插件&#xff0c;完成后可能需要重启 Jenkins。 步骤 2&#xff1a;配置…

ultralytics实现DeepSort目标追踪算法之特征提取网络

文章目录 DeepSort基本流程DeepSort特征提取网络Market-1501数据集目录结构命名规则 数据集划分 网络模型训练过程参数设置数据集加载特征提取网络定义预训练模型加载损失函数与优化器定义mian函数调用训练过程验证过程平均指标与结果 DeepSort基本流程 DeepSort&#xff08;D…

数据结构栈和队列

系统栈 程序运行中使用的栈&#xff0c;由操作系统维护 栈区&#xff1a;1&#xff0c;保存局部变量 2&#xff0c;函数的形参的返回值 3&#xff0c;函数的调用关系 函数中调用函数时会把调用函数的下一条指定的首地址保存在栈区。 &#xff08;保护现…

Steam游戏截图方法

Steam游戏截图方法 截图快捷键 Steam游戏自带截图功能&#xff0c;在游戏中无需复杂的快捷键&#xff0c;仅需按下F12快捷键便可立即截图&#xff0c;官方说明如下。下文介绍使用方法。 查看截图 退出游戏后&#xff0c;在Steam界面点击查看 - 截图&#xff0c;即可查看截…

JAVA—反射

学习Java中关于反射的知识&#xff0c;以理解框架 目录 1.认识反射 2.获取类 3.获取构造器 4.获取成员变量​编辑 5.获取成员方法 6.作用 应用场景 1.认识反射 反射 加载类 并允许以编程的方式解刨类中的各种成分&#xff08;成员变量 方法 构造器&#xff09; 学习反射…

【Hadoop|HDFS篇】HDFS的读写流程

1. HDFS的写流程 1.1 剖析文件的写入 副本存储节点的选择问题&#xff1a; 第一个副本在Client所在的节点上&#xff0c;如果客户端在集群外&#xff0c;随机选一个。第二个副本在另一个机架的随机一个节点上。第三个副本在第二个副本所在的机架的随机节点上。 2. HDFS的写流…

机器学习和物联网驱动技术在加工过程中监测工具磨损:一项全面的综述

这篇论文的标题是《Machine-Learning and Internet-of-Things-Driven Techniques for Monitoring Tool Wear in Machining Process: A Comprehensive Review》&#xff0c;由 Sudhan Kasiviswanathan、Sakthivel Gnanasekaran、Mohanraj Thangamuthu 和 Jegadeeshwaran Rakkiya…

超越传统:Reflection 70B如何革新AI语言处理

Reflection 70B&#xff1a;AI语言模型的新里程碑&#x1f680; AI领域迎来了革命性的变革&#xff0c;HyperWrite公司推出的开源AI大模型Reflection 70B&#xff0c;以其卓越的性能在多个基准测试中超越了GPT-4o和Llama 3.1。这款基于Meta的Llama 3.1 70B Instruct构建的模型…

为何iPhone 16系列的发布对苹果至关重要?

即将发布的iPhone 16系列对苹果来说将是至关重要的时刻&#xff0c;特别是在快速发展的AI智能手机市场背景下。随着Android制造商在集成先进AI功能方面领先一步&#xff0c;苹果正处于一个关键的转折点——赶上竞争对手不仅仅是选择&#xff0c;而是必须完成的任务。 AI竞赛&am…

上交2024最新-《动手学大模型》实战教程及ppt分享!

本课介绍 今天分享一个上海交大的免费的大模型课程&#xff0c;有相关教程文档和Slides&#xff0c;目前是2.2K星标&#xff0c;还是挺火的&#xff01; 《动手学大模型》系列编程实践教程&#xff0c;由上海交通大学2024年春季《人工智能安全技术》课程&#xff08;NIS3353&…

传统CV算法——基于harris检测算法实现角点检测

角点 角点是图像中的一个特征点&#xff0c;指的是两条边缘交叉的点&#xff0c;这样的点在图像中通常表示一个显著的几角。在计算机视觉和图像处理中&#xff0c;角点是重要的特征&#xff0c;因为它们通常是图像中信息丰富的区域&#xff0c;可以用于图像分析、对象识别、3D…

如何在极狐GitLab中添加 SSH Key?

本文分享如何生成 SSH Key 并添加到极狐GitLab 中&#xff0c;然后用 SSH Key 进行代码拉取。 极狐GitLab 是 GitLab 在中国的发行版&#xff0c;可以私有化部署&#xff0c;对中文的支持非常友好&#xff0c;是专为中国程序员和企业推出的企业级一体化 DevOps 平台&#xff0…

43. 1 ~ n 整数中 1 出现的次数【难】

comments: true difficulty: 中等 edit_url: https://github.com/doocs/leetcode/edit/main/lcof/%E9%9D%A2%E8%AF%95%E9%A2%9843.%201%EF%BD%9En%E6%95%B4%E6%95%B0%E4%B8%AD1%E5%87%BA%E7%8E%B0%E7%9A%84%E6%AC%A1%E6%95%B0/README.md 面试题 43. 1 &#xff5e; n 整数中 1 …

C#游戏服务器开发框架设计与架构详解

我一直在思考一个问题&#xff0c;什么样的服务端框架最好用&#xff0c;最适合? 经过这些年的项目经验&#xff0c;其实最好用&#xff0c;最适合的游戏服务端框架就是自己结合公司项目需求,团队特点与技术能力,自己整合的游戏框架是最好用的。 很多新手会担心自己整合的框架…

JS生成二维码QRCode代码

JavaScript是一种广泛使用的前端编程语言&#xff0c;它不仅用于网页交互&#xff0c;还可以实现许多实用功能&#xff0c;如生成二维码。本篇文章将深入探讨如何使用JavaScript生成二维码&#xff0c;以及如何确保这种生成的二维码在各种浏览器和手机端都能正常工作&#xff0…

找不同-第15届蓝桥省赛Scratch初级组真题第4题

[导读]&#xff1a;超平老师的《Scratch蓝桥杯真题解析100讲》已经全部完成&#xff0c;后续会不定期解读蓝桥杯真题&#xff0c;这是Scratch蓝桥杯真题解析第183讲。 如果想持续关注Scratch蓝桥真题解读&#xff0c;可以点击《Scratch蓝桥杯历年真题》并订阅合集&#xff0c;…

GAMES101(0~1作业)

搭建虚拟机环境 安装Oracle VM VirtualBox虚拟机&#xff0c;安装虚拟硬盘&#xff0c;配置Linux Ubuntu-64 bit系统&#xff0c;启动虚拟机&#xff0c;发生冲突错误&#xff1a; 将Vmware虚拟设备取消挂起状态&#xff0c;关机确保 Hyper-V 完全关闭&#xff1a;bcdedit /se…

【Python · Pytorch】配置cuda环境 cuDNN库

【Python Pytorch】配置cuda环境 & cuDNN库 1. 查找对应版本1.1 查看Pytorch GPU目前支持版本1.1 查看Nvidia驱动版本1.2 查看支持cuda版本1.3 查看支持cuDNN版本1.3.1 cuDNN 9.0.0及以上版本1.3.2 cuDNN 9.0.0以下版本 1.4 安装版本确定 2. 安装cuda环境2.1 cuda简介2.1.…