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

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

【OSG学习笔记】Day 18: 碰撞检测与物理交互

物理引擎&#xff08;Physics Engine&#xff09; 物理引擎 是一种通过计算机模拟物理规律&#xff08;如力学、碰撞、重力、流体动力学等&#xff09;的软件工具或库。 它的核心目标是在虚拟环境中逼真地模拟物体的运动和交互&#xff0c;广泛应用于 游戏开发、动画制作、虚…...

基于当前项目通过npm包形式暴露公共组件

1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹&#xff0c;并新增内容 3.创建package文件夹...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...

Linux离线(zip方式)安装docker

目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1&#xff1a;修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本&#xff1a;CentOS 7 64位 内核版本&#xff1a;3.10.0 相关命令&#xff1a; uname -rcat /etc/os-rele…...

用鸿蒙HarmonyOS5实现中国象棋小游戏的过程

下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...

TCP/IP 网络编程 | 服务端 客户端的封装

设计模式 文章目录 设计模式一、socket.h 接口&#xff08;interface&#xff09;二、socket.cpp 实现&#xff08;implementation&#xff09;三、server.cpp 使用封装&#xff08;main 函数&#xff09;四、client.cpp 使用封装&#xff08;main 函数&#xff09;五、退出方法…...

小智AI+MCP

什么是小智AI和MCP 如果还不清楚的先看往期文章 手搓小智AI聊天机器人 MCP 深度解析&#xff1a;AI 的USB接口 如何使用小智MCP 1.刷支持mcp的小智固件 2.下载官方MCP的示例代码 Github&#xff1a;https://github.com/78/mcp-calculator 安这个步骤执行 其中MCP_ENDPOI…...

在ubuntu等linux系统上申请https证书

使用 Certbot 自动申请 安装 Certbot Certbot 是 Let’s Encrypt 官方推荐的自动化工具&#xff0c;支持多种操作系统和服务器环境。 在 Ubuntu/Debian 上&#xff1a; sudo apt update sudo apt install certbot申请证书 纯手动方式&#xff08;不自动配置&#xff09;&…...