当前位置: 首页 > news >正文

定高虚拟列表:让大数据渲染变得轻松

定高虚拟列表

基本认识

在数据如潮水般涌来的今天,如何高效地展示和管理这些数据成为了开发者们面临的一大挑战,传统的列表渲染方式在处理大量数据时,往往会导致页面卡顿、滚动不流畅等问题,严重影响用户体验(在页面渲染大量的 dom 时,不仅是在第一次渲染比较影响性能外,在后续的每一次回流或重绘时,也会造成巨大的性能的问题) - 我们需要知道 JS 执行永远要比 DOM 快的多

然而,有一种技术能够在不牺牲用户体验的前提下,显著提升大数据集的处理效率,它就是——虚拟列表

虚拟列表 实际上是一种实现方案,只对 可视区域 进行渲染,对 非可视区域 中的区域不渲染或只渲染一部分(渲染的部分叫 缓冲区,不渲染的部分叫 虚拟区),从而达到极高的性能

虚拟列表也分为定高虚拟列表与非定高虚拟列表等,定高虚拟列表的意思就就是说列表中每一项的高度需要是一样的,反之不定高就是列表中的每一项高度都是不缺订单 - 我们这里先来学一下定高虚拟列表的实现方式(后续再继续更新不定高虚拟列表的实现方式)

我们可以先通过一张图来简单分析定高虚拟列表

  • 在这里插入图片描述

  • 从图中可以看出,我们可以将列表分为三个区域:可视区缓冲区虚拟区

  • 而我们主要针对 可视区缓冲取 进行渲染

基本实现

这里采用 vue3 来实现,如果使用其它框架的或原生,对应实现思路基本也是差不多的

HTML 页面布局

  • 容器元素 - 我们需要有一个可视窗口的容器,可以是一整个屏幕视口(也可以通过指定对应容器 .virtual-list-container 的高度,并使该元素作为可视区域)

  • 占位区域 - 在容器中我们还需要一个占位元素,用于撑开容器的高度使其容器可以存在对应的滚动条(因为容器中只渲染可视区域中的元素,所以我们还需要该占位元素来根据列表的数据量来撑出对应滚动条的高度)

  • 内容区域 - 最后我们还需要有一个可视区域的容器,这块部分为真正用户看到的列表区域,对应渲染的数据实际上是 可视区域缓存区域 的列表数据 → tip: 缓冲区的作用是防止快速下滑或者上滑的过程中出现空白区域

  • <!-- 容器元素 -->
    <div class="virtual-list-container"><!-- 占位元素 --><div class="virtual-list-placeholder"></div><!-- 渲染区域 --><div class="virtual-list-content"><!-- 子组件: 列表中的每一项 --></div>
    </div>
    + tip: 这里只是一个简单的结构,因为后续还有需要在这些结构中进行一些动态的修改
    

在 JS 中我们需要的状态

  • list - 列表数据(所有数据)

  • showList - 所要真正展示的数据(在所有数据列表 list 中进行 slice 裁剪而得,根据下面的 startend 进行个数的裁剪)

  • itemHeight - 列表中每一项的高度

  • renderCount - 可视区可以渲染的项目数量(根据 可视区域的高度 / itemHeight 计算出来,因为基本都会有小数问题的,可以通过 Math 来进行处理一下)

  • bufferCount - 缓存区的项目个数

  • start - 截取展示数据的开始索引

  • end - 截取展示数据的结束索引(根据 start + renderCount + bufferCount 获取,但是可能会超出整个列表,所有需要根据该计算出来的值需要根据 list.length 取最小值,避免 end 超出列表的数据量)

  • currentOffset - 滚动偏移量,因为可视区域渲染的元素的个数是基本固定的,顺着滚动条的滚动必然也会向上滚动,所以可以根据该值对可视区域进行相应偏移量的平移,避免随着滚动条滚动(根据 滚动大小 - (滚动大小 % itemHeight) 进行获取)

代码实现:

  • HTML 结构

    • <template><!-- 容器 --><div class="virtual-list-container" ref="virtual-list-container"><!-- 占位元素: 根据列表数据个数与每一项的大小计算出对应的高度 --><div class="virtual-list-placeholder" :style="{ height: placeholderHeight + 'px' }"></div><!-- 渲染区域: 根据对应的 currentOffset 偏移量,将渲染区域进行对应的平移 --><div class="virtual-list-content" :style="{ transform: `translateY(${state.currentOffset}px)` }"><!-- 子组件: 循环列表中的每一项 --><div class="item" v-for="item in showList" :key="item">{{ item }}</div></div></div>
      </template>
      
  • CSS 样式

    • <style scoped lang="scss">
      .virtual-list {&-container { /* 容器大小 */width: 100%;height: 100vh;position: relative;overflow: auto;}&-placeholder {position: absolute;inset: 0;}&-content {position: absolute;inset: 0;}
      }.item { /* item 列表项样式 */outline: 1px solid orange;height: 60px;line-height: 60px;text-align: center;
      }
      </style>
      
  • JS 部分

    • <script setup>
      import { computed, onMounted, reactive, useTemplateRef } from 'vue';const list = new Array(20000).fill(null).map((item, i) => i + 1) // -- 模拟列表数据(所有数据)
      const containerRef = useTemplateRef("virtual-list-container") // -- 容器元素// -- 获取相应的状态
      const state = reactive({start: 0, // -- 开始索引itemHeight: 60, // -- 每项 item 中的高度renderCount: 0, // -- 可视区域可以渲染的项目个数(根据容器高度与 itemHeight 进行计算)bufferCount: 6, // -- 缓存个数currentOffset: 0, // -- 根据滚动距离获取对应的偏移量
      })const end = computed(() => // -- 计算结束索引Math.min(state.start + state.renderCount + state.bufferCount,list.length)
      )const showList = computed(() => list.slice(state.start, end.value)) // -- 根据 start 与 end 获取对应需要展示的列表数据onMounted(() => {const containerHeight = containerRef.value.offsetHeight // -- 获取容器高度state.renderCount = Math.round(containerHeight / state.itemHeight) // -- 获取可视区域可渲染个数
      })const placeholderHeight = list.length * state.itemHeight // -- 占位元素的高度(用于给容器撑出对应的滚动条)
      </script>
      
  • 上面的代码已经可以根据对应的视口大小,来计算所需要渲染的个数了,但是还有一个滚动时的处理,为了方便大家阅读,所以将滚动条滚动部分的代码放在这下面(这些代码直接与上面 JS 部分 进行合并即可)

    • const handleScrollEvent = () => { // -- 触发滚动事件时,所需要处理的操作if (!containerRef.value) returnconst { scrollTop } = containerRef.value// -- 根据滚动大小,重新计算 start 开始索引(因为 end 结束索引是根据该 start 派生的计算属性,所以也会自动的进行计算)state.start = Math.round(scrollTop / state.itemHeight)// -- 根据滚动大小,重新计算对应滚动的偏移量,用于动态的给可视区域进行对应偏移量的平移state.currentOffset = scrollTop - (scrollTop & state.itemHeight)
      }onMounted(() => {if (!containerRef.value) returncontainerRef.value.addEventListener("scroll", handleScrollEvent) // -- 监听容器的滚动
      })onUnmounted(() => {if (!containerRef.value) returncontainerRef.value.removeEventListener("scroll", handleScrollEvent) // -- 组件卸载时取消监听滚动事件
      })
      
  • 通过上面的代码我们就可以简单的实现了一个定高的虚拟列表了

注意:

  • 上面的代码虽然实现了定高的虚拟列表,单滚动条的触发频率我们可以通过稍加一点点 节流(Throttle)防抖(Debounce) 来降低回调函数的触发频率(当然使用防抖还是节流具体看你想要的是一种怎么样的效果)
  • 当然,我们也可以通过 Intersection Observer API 来代替滚动事件的监听,来提高对应的性能

总结: 定高虚拟列表实现思路

1. 在 HTML 结构中的处理

  • (1) - 需要有一个可视窗口的容器

  • (2) - 容器中需要有一个占位元素,用来撑开视口高度,使其能够有对应高度的滚动条(该元素的高度需要等于: 长列表中的数据长度 * 每个元素的高度 → tip: 如果元素存在 margin 等也需要计算上去

  • (3) - 容器中还需要有一个内容区域(用来存放真正渲染的列表数据),该元素需要是一个根据容器元素的一个绝对定位元素(用于当滚动条在滚动时,该内容区域元素可以根据定位中的 top 属性进行相应滚动距离的平移,避免整个容器区域也随着滚动上去(当然也可以通过 transform 进行平移)

2. 在 JS 中的处理

  • list - 列表数据(所有数据)

  • showList - 所要真正展示的数据(在所有数据列表 list 中进行 slice 裁剪而得,根据下面的 startend 进行个数的裁剪)

  • itemHeight - 列表中每一项的高度

  • renderCount - 可视区可以渲染的项目数量(根据 可视区域的高度 / itemHeight 计算出来,因为基本都会有小数问题的,可以通过 Math 来进行处理一下)

  • bufferCount - 缓存区的项目个数

  • start - 截取展示数据的开始索引

  • end - 截取展示数据的结束索引(根据 start + renderCount + bufferCount 获取,但是可能会超出整个列表,所有需要根据该计算出来的值需要根据 list.length 取最小值,避免 end 超出列表的数据量)

  • currentOffset - 滚动偏移量,因为可视区域渲染的元素的个数是基本固定的,顺着滚动条的滚动必然也会向上滚动,所以可以根据该值对可视区域进行相应偏移量的平移,避免随着滚动条滚动(根据 滚动大小 - (滚动大小 % itemHeight) 进行获取)

3. onscroll 监听滚动条 : 在滚动条监听的回调中,主要需要做如下两件事

  • (1) - 获取当前的滚动大小,并根据该值与对应的 itemHeight 大小计算出新的 start,并赋值该对应的 start 状态中
  • (2) - 根据滚动大小,动态设置 HTML 中的列表容器(.virtual-list)中的 top 样式,进行对应的平移 → 避免列表容器也随着滚动条的滚动而进行滚动

相关文章:

定高虚拟列表:让大数据渲染变得轻松

定高虚拟列表 基本认识 在数据如潮水般涌来的今天&#xff0c;如何高效地展示和管理这些数据成为了开发者们面临的一大挑战&#xff0c;传统的列表渲染方式在处理大量数据时&#xff0c;往往会导致页面卡顿、滚动不流畅等问题&#xff0c;严重影响用户体验&#xff08;在页面…...

python request与grequests该如何选择

requests & grequests requests 和 grequests 是Python中用于发送HTTP请求的不同库。requests 是一个同步、阻塞式库&#xff0c;而 grequests 是基于 requests 封装的异步非阻塞库&#xff0c;它利用了 gevent 库提供的协程机制&#xff0c;能够并发发送多个请求。 选择…...

Unity3D UI 拖拽

Unity3D 实现 UI 元素拖拽功能。 UI 拖拽 通常画布上的 UI 元素都是固定位置的&#xff0c;我们可以通过实现拖拽接口&#xff0c;让 UI 元素可以被拖拽到其他位置。 拖拽接口 创建一个脚本 UIDrag.cs&#xff0c;在默认继承的 MonoBehaviour 后面&#xff0c;再继承三个接…...

介绍一下memcpy(c基础)

memcpy函数void *memcpy(void *dest, const void *src, size_t n); dest&#xff1a;指向目标内存区域的指针&#xff0c;即复制的目的地。src&#xff1a;指向源内存区域的指针&#xff0c;即要被复制的内容的来源。n&#xff1a;要复制的字节数 主要功能是将src所指向的内存…...

【网络面试篇】HTTP(2)(笔记)——http、https、http1.1、http2.0

目录 一、相关面试题 1. HTTP 与 HTTPS 有哪些区别&#xff1f; 2. HTTPS 的工作原理&#xff1f;&#xff08;https 是怎么建立连接的&#xff09; &#xff08;1&#xff09;ClientHello &#xff08;2&#xff09;SeverHello &#xff08;3&#xff09;客户端回应 &a…...

python-23-一篇文章帮你理解Python推导式

python-23-一篇文章帮你理解Python推导式 一.简介 在 Python 中&#xff0c;推导式&#xff08;Comprehensions&#xff09;是一个简洁的语法&#xff0c;用于通过某种可迭代对象快速生成新的对象&#xff08;如列表、字典、集合等&#xff01;来开始我们今天的日拱一卒&…...

WPF中如何简单的使用CommunityToolkit.Mvvm创建一个项目并进行 增删改查

目录 开始前准备的数据库dbblog如下&#xff1a; 第一步&#xff1a;创建项目后下载四个NuGet程序包 第二步&#xff1a;删除原本的MainWindow.XAML文件 并创建如下的目录结构 然后在View文件夹下面创建Login.XAML和Main.XAML 并且在App.XAML中将启动项改为Login.X…...

CesiumJS 案例 P15:检测标记、鼠标点击移动标记、鼠标拖动标记

CesiumJS CesiumJS API&#xff1a;https://cesium.com/learn/cesiumjs/ref-doc/index.html CesiumJS 是一个开源的 JavaScript 库&#xff0c;它用于在网页中创建和控制 3D 地球仪&#xff08;地图&#xff09; 一、检测标记 <!DOCTYPE html> <html lang"en&…...

Webserver(4.9)本地套接字的通信

目录 本地套接字 本地套接字 TCP\UDP实现不同主机、网络通信 本地套接字实现本地的进程间的通信&#xff0c;类似的&#xff0c;一般采用TCP的通信流程 生成套接字文件 #include<arpa/inet.h> #include<stdio.h> #include<stdlib.h> #include<unistd.h&…...

[IAA系列] Image Aesthetic Assessment

Preface 本文旨在记录个人结合AI工具对IAA这个领域的一些了解&#xff0c;主要是通过论文阅读的方式加深对领域的了解。有什么问题&#xff0c;欢迎在评论区提出并讨论。 什么是IAA Image Aesthetic Assessment&#xff08;图像美学评估&#xff09;是一种评估图像在视觉上的…...

基于springboot的高校科研管理系统(源码+调试+LW)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据你想解决的问题&#xff0c;今天给…...

Flutter环境配置

配置环境变量 PUB_HOSTED_URLhttps://pub.flutter-io.cn FLUTTER_STORAGE_BASE_URLhttps://storage.flutter-io.cn 这个命令是用来配置 Flutter 的镜像源地址&#xff0c;主要是为了解决在中国大陆地区访问 Flutter 官方资源较慢的问题 具体的操作做如下: 右键点击"此…...

Rip动态路由及Rip动态路由优化

动态路由Rip Tip&#xff1a;Rip动态路由实现多个路由间不同网段通信。 本次实验目的&#xff0c;通过给ar1,ar2,ar3配置rip动态路由&#xff0c;实现pc1 ping通 pc2。 AR1配置如下&#xff1a; <Huawei>sy Enter system view, return user view with CtrlZ. [Huawei]…...

双路快速排序和三路排序算法

双路快速排序 一、概念及其介绍 双路快速排序算法是随机化快速排序的改进版本&#xff0c;partition 过程使用两个索引值&#xff08;i、j&#xff09;用来遍历数组&#xff0c;将 <v 的元素放在索引i所指向位置的左边&#xff0c;而将 >v 的元素放在索引j所指向位置的…...

SQL server增删改查语句和实例

在 SQL Server 中&#xff0c;增删改查操作分别对应 INSERT、DELETE、UPDATE 和 SELECT 语句。以下是具体介绍及实例&#xff1a; 一、插入数据&#xff08;INSERT&#xff09; 语法&#xff1a; INSERT INTO table_name (column1, column2, column3,...) VALUES (value1, val…...

强化学习_06_pytorch-PPO2实践(ALE/Breakout-v5)

一、环境适当调整 数据收集&#xff1a;RecordEpisodeStatistics进行起始跳过n帧&#xff1a;baseSkipFrame一条生命结束记录为done:EpisodicLifeEnv得分处理成0或1:ClipRewardEnv叠帧: FrameStack 图像环境的基本操作&#xff0c;方便CNN捕捉智能体的行动 向量空间reset处理修…...

《JVM第8课》垃圾回收算法

文章目录 1.标记算法1.1 引用计数法1.2 可达性分析法 2.回收算法2.1 标记-清除算法&#xff08;Mark-Sweep&#xff09;2.2 复制算法&#xff08;Coping&#xff09;2.3 标记-整理算法&#xff08;Mark-Compact&#xff09; 3.三种垃圾回收算法的对比 为什么要进行垃圾回收&…...

SpringBoot整合Freemarker(二)

if分支 语法&#xff1a; <#if condition>... <#elseif condition2>... <#elseif condition3>... <#else>... </#if> 例子&#xff1a; <#if x 1>x is 1 </#if> --------------------------------- <#if x 1>x is 1 <…...

element plus el-form自定义验证输入框为纯数字函数

element plus 的el-form 使用自定义验证器&#xff0c;验证纯数字&#xff0c;禁止输入小数、中文、字母、特殊符号。input的maxlength为最大输入多少位长度 效果图 <el-form ref"dataFormRef" :model"dataForm" :rules"dataRules" label-w…...

Android笔记(三十一):Deeplink失效问题

背景 通过deeplink启动应用之后&#xff0c;没关闭应用的情况下&#xff0c;再次使用deeplink会失效的问题&#xff0c;是系统bug导致的。此bug仅在某些设备&#xff08;Nexus 5X&#xff09;上重现&#xff0c;launchMode并且仅当应用程序最初通过深层链接启动并再次通过深层…...

centos 7 部署awstats 网站访问检测

一、基础环境准备&#xff08;两种安装方式都要做&#xff09; bash # 安装必要依赖 yum install -y httpd perl mod_perl perl-Time-HiRes perl-DateTime systemctl enable httpd # 设置 Apache 开机自启 systemctl start httpd # 启动 Apache二、安装 AWStats&#xff0…...

深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法

深入浅出&#xff1a;JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中&#xff0c;随机数的生成看似简单&#xff0c;却隐藏着许多玄机。无论是生成密码、加密密钥&#xff0c;还是创建安全令牌&#xff0c;随机数的质量直接关系到系统的安全性。Jav…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

如何应对敏捷转型中的团队阻力

应对敏捷转型中的团队阻力需要明确沟通敏捷转型目的、提升团队参与感、提供充分的培训与支持、逐步推进敏捷实践、建立清晰的奖励和反馈机制。其中&#xff0c;明确沟通敏捷转型目的尤为关键&#xff0c;团队成员只有清晰理解转型背后的原因和利益&#xff0c;才能降低对变化的…...

uniapp 小程序 学习(一)

利用Hbuilder 创建项目 运行到内置浏览器看效果 下载微信小程序 安装到Hbuilder 下载地址 &#xff1a;开发者工具默认安装 设置服务端口号 在Hbuilder中设置微信小程序 配置 找到运行设置&#xff0c;将微信开发者工具放入到Hbuilder中&#xff0c; 打开后出现 如下 bug 解…...

python打卡day49@浙大疏锦行

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 一、通道注意力模块复习 & CBAM实现 import torch import torch.nn as nnclass CBAM(nn.Module):def __init__…...

react更新页面数据,操作页面,双向数据绑定

// 路由不是组件的直接跳转use client&#xff0c;useEffect&#xff0c;useRouter&#xff0c;需3个结合&#xff0c; use client表示客户端 use client; import { Button,Card, Space,Tag,Table,message,Input } from antd; import { useEffect,useState } from react; impor…...

JUC并发编程(二)Monitor/自旋/轻量级/锁膨胀/wait/notify/锁消除

目录 一 基础 1 概念 2 卖票问题 3 转账问题 二 锁机制与优化策略 0 Monitor 1 轻量级锁 2 锁膨胀 3 自旋 4 偏向锁 5 锁消除 6 wait /notify 7 sleep与wait的对比 8 join原理 一 基础 1 概念 临界区 一段代码块内如果存在对共享资源的多线程读写操作&#xf…...

CentOS 7.9安装Nginx1.24.0时报 checking for LuaJIT 2.x ... not found

Nginx1.24编译时&#xff0c;报LuaJIT2.x错误&#xff0c; configuring additional modules adding module in /www/server/nginx/src/ngx_devel_kit ngx_devel_kit was configured adding module in /www/server/nginx/src/lua_nginx_module checking for LuaJIT 2.x ... not…...

Gitlab + Jenkins 实现 CICD

CICD 是持续集成&#xff08;Continuous Integration, CI&#xff09;和持续交付/部署&#xff08;Continuous Delivery/Deployment, CD&#xff09;的缩写&#xff0c;是现代软件开发中的一种自动化流程实践。下面介绍 Web 项目如何在代码提交到 Gitlab 后&#xff0c;自动发布…...