当前位置: 首页 > 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并且仅当应用程序最初通过深层链接启动并再次通过深层…...

图神经网络初步实验

实验复现来源 https://zhuanlan.zhihu.com/p/603486955 该文章主要解决问题&#xff1a; 1.加深对图神经网络数据集的理解 2.加深对图神经网络模型中喂数据中维度变化的理解 原理问题在另一篇文章分析&#xff1a; 介绍数据集&#xff1a;cora数据集 其中的主要内容表示为…...

创建线程时传递参数给线程

在C中&#xff0c;可以使用 std::thread 来创建和管理线程&#xff0c;同时可以通过几种方式将参数传递给线程函数。这些方法包括使用值传递、引用传递和指针传递。下面将对这些方法进行详细讲解并给出相应的代码示例。 1. 值传递参数 当你创建线程并希望传递参数时&#xff…...

兴业严选|美国总统都是不良资产出身 法拍市场是否将大众化

北京时间11月6日&#xff0c;特朗普赢得美国大选。 说起特朗普那就不得不提他的发家史&#xff0c;那可真是一笔笔不良资产投资堆出来的。 没错&#xff0c;特朗普就是处理不良资产的高手&#xff0c;战果丰硕。 改造斯威夫特小镇、 康莫德酒店、打造特朗普&#xff08;TRUM…...

C#-拓展方法

概念&#xff1a;为现有的非静态变量类型&#xff0c;添加方法 语法&#xff1a; 访问修饰符 static 返回值 函数名(this 拓展类名 参数名, 参数类型 参数名,参数类型 参数名....){} 而public static void F(this Console()){ }是错的。Console是静态类不可以为静态类添加方…...

加锁失效,非锁之过,加之错也|京东零售供应链库存研发实践

本文导读 从事京东零售供应链库存业务&#xff0c;库存数量操作增减十分频繁&#xff0c;并且项目开发中会常常遇到各种并发情况&#xff0c;一旦库存数量操作有误&#xff0c;势必给前台销售产生损失影响&#xff0c;因此需要关注对库存数量并发操作下的一致性问题。 大部分…...

vue3 传值的几种方式

一.父组件传子组件 父组件 //父组件 <Decisionobject :Decisionselected"Decisionselected"></Decisionobject> <script lang"ts" setup> let Decisionselected ref(false); </script>子组件 <script lang"ts" s…...

AUTOSAR CP NVRAM Manager规范导读

一、NVRAM Manager功能概述 NVRAM Manager是AUTOSAR(AUTomotive Open System ARchitecture)框架中的一个模块,负责管理非易失性随机访问存储器(NVRAM)。它提供了一组服务和API,用于在汽车环境中存储、维护和恢复NV数据。以下是NVRAM Manager的一些关键功能: 数据存储和…...

2024阿里云CTF Web writeup

《Java代码审计》http://mp.weixin.qq.com/s?__bizMzkwNjY1Mzc0Nw&mid2247484219&idx1&sn73564e316a4c9794019f15dd6b3ba9f6&chksmc0e47a67f793f371e9f6a4fbc06e7929cb1480b7320fae34c32563307df3a28aca49d1a4addd&scene21#wechat_redirect 前言 又是周末…...

软件著作权申请教程(超详细)(2024新版)软著申请

目录 一、注册账号与实名登记 二、材料准备 三、申请步骤 1.办理身份 2.软件申请信息 3.软件开发信息 4.软件功能与特点 5.填报完成 一、注册账号与实名登记 首先我们需要在官网里面注册一个账号&#xff0c;并且完成实名认证&#xff0c;一般是注册【个人】的身份。中…...

三维测量与建模笔记 - 3.2 直接线性变换法标定DLT

DLT - Direct Linear Transform 上图中&#xff0c;透视成像对应的公式是共线方程&#xff0c;可以参考以下链接&#xff1a; https://zhuanlan.zhihu.com/p/101549821https://zhuanlan.zhihu.com/p/101549821 对于标定来说&#xff0c;需要找到。已知量是。 (u,v)是…...