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

用原生JS实现虚拟列表(IT枫斗者)

用原生JS实现虚拟列表

介绍

  • 最近在开发需求的时候,有用到 Antd 的虚拟列表组件 rc-virtual-list ,粗略地看了一下源码,于是萌生了自己写一个虚拟列表的想法。
  • 当一个列表需要渲染大量数据的时候是非常耗时的,而且在列表滚动的过程中会出现卡顿的现象。即使用上懒加载解决了列表初始化时渲染过慢的问题,但是每次拉取下一页数据的时候都会造成列表的重新渲染。随着拉取的数据越来越多,列表渲染时间长、卡顿的问题依然存在。
  • 这个时候虚拟列表就派上用场了。虚拟列表的实现原理简单来说,就是列表并不会把所有的数据都渲染出来,而是通过监听滚动事件然后实时计算当前是哪几条数据显示在页面上,然后只渲染用户可以看见的这几条数据。
  • 在网上找了张图可以说明的更生动些:
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vbz6hBM5-1684195418798)(C:\Users\quyanliang\AppData\Roaming\Typora\typora-user-images\1684195219996.png)]

定高虚拟列表

  • 实现虚拟列表需要两层的div:

  • <style>.list-container {overflow: auto;border: 1px solid black;height: 500px;}
    </style><body><!-- 外部容器用来固定列表容器的高度,同时生成滚动条 --><div class="list-container"><!-- 内部容器用来装元素,高度是所有元素高度的和 --><div class="list-container-inner"></div></div>Ï
    </body>
    
  • 外层的div用来固定高度,也就是图上展示的B区域。内层的div用来装列表元素,也就是图上展示的A区域。给外层的div设置 overflow: auto ,这样当列表元素数量超过B区域的高度时就会出现滚动条。

  • 接下来,我们需要监听外层div的滚动事件,实时计算出显示在可视区域上的第一个元素的坐标 startIndex 和 最后一个元素的坐标 endIndex。假设列表每个元素的高度都是60px,那么 startIndex 就等于外层 div 顶部卷起来的长度除以列表元素的高度,而 endIndex 就等于 startIndex + 可视区域能展示的元素个数 :

  • const itemHeight = 60
    const height = 500
    const startIndex = Math.floor(outerContainer.scrollTop / itemHeight)
    const endIndex = startIndex + Math.ceil(height / itemHeight)
    
  • 然后用 startIndex 和 endIndex 去截取数据,并渲染在内层 div 上:

  • const viewData = data.slice(startIndex, endIndex + 1)
    const innerContainer = document.querySelector('.list-container-inner')
    innerContainer.innerHTML = ''
    for(let i = 0; i < viewData.length; i++) {const item = document.createElement('div')item.innerHTML = viewData[i]innerContainer.appendChild(item)
    }
    
  • 最后一步,虽然我们只需要渲染在可视区域上的元素,但是我们还需要给内层的 div 设置 padding-top 和 padding-bottom ,替代那些不需要渲染的元素。由此保证虚拟列表是可以滚动的,而且滚动条位置和列表元素位置是相对应的。 padding-top 等于 startIndex 之前的元素高度和, padding-bottom 等于 endIndex 之后的元素高度和:

  • const paddingTop = startIndex * itemHeight
    const paddingBottom = (data.length - endIndex) * itemHeight
    innerContainer.setAttribute('style', `padding-top: ${paddingTop}px; padding-bottom: ${paddingBottom}px`)
    
  • 完整代码:

  • <style>.list-container {overflow: auto;border: 1px solid black;height: 500px;}
    </style><body><!-- 外部容器用来固定列表容器的高度,同时生成滚动条 --><div class="list-container"><!-- 内部容器用来装元素,高度是所有元素高度的和 --><div class="list-container-inner"></div></div><script>/** --------- 一些基本变量 -------- */const itemHeight = 60const height = 500/** --------- 生成数据 -------- */const initData = () => {const data = []for(let i = 0; i < 15; i++) {data.push({content: `内容:${i}`, height: itemHeight, color: i % 2 ? 'red' : 'yellow'})}return data}const data = initData()const contentHeight = itemHeight * data.lengthconst outerContainer = document.querySelector('.list-container')const scrollCallback = () => {// 获取当前要渲染的元素的坐标const scrollTop = Math.max(outerContainer.scrollTop, 0)const startIndex = Math.floor(scrollTop / itemHeight)const endIndex = startIndex + Math.ceil(height / itemHeight)const innerContainer = document.querySelector('.list-container-inner')// 从data取出要渲染的元素并渲染到容器中const viewData = data.slice(startIndex, endIndex + 1)innerContainer.innerHTML = ''for(let i = 0; i < viewData.length; i++) {const item = document.createElement('div')const itemData = viewData[i]item.innerHTML = itemData.contentitem.setAttribute('style', `height: ${itemData.height}px; background: ${itemData.color}`)innerContainer.appendChild(item)}// 未渲染的元素由padding-top和padding-bottom代替,保证滚动条位置正确const paddingTop = startIndex * itemHeightconst paddingBottom = (data.length - endIndex) * itemHeightinnerContainer.setAttribute('style', `padding-top: ${paddingTop}px; padding-bottom: ${paddingBottom}px`)}// 首屏渲染scrollCallback()// 监听外部容器的滚动事件outerContainer.addEventListener('scroll', scrollCallback)</script>
    </body>
    

不定高虚拟列表

  • 上面实现的是元素高度固定的虚拟列表,对于元素高度不固定的情况,虚拟列表实现起来会更复杂一些。

  • 因为我们事先不知道元素的高度,所以在监听滚动事件的过程中,需要遍历一遍列表获取每个元素的高度,由此算出 startIndex 、 endIndex 等数据。不过在第一次渲染之前,因为元素没有渲染出来,我们拿不到元素的真实高度。所以这时候需要给元素一个最小高度 itemHeight ,通过 itemHeight 来计算 startIndex 和 endIndex,由此保证第一次渲染出来的元素能占满整个可视区域。

  • 另外,当元素渲染出来以后,我们用一个字典去记录每个元素的真实高度,供下次滚动事件触发时 startIndex 和 endIndex 的计算。

  • startIndex 的算法是在遍历列表元素的过程中,逐步累加当前元素的高度得到 contentHeight ,当第一次出现 contentHeight 大于容器顶部卷起来的长度的时候,说明当前元素是列表可视区域的第一个元素,记为 startIndex。

  • endIndex 的算法是当第一次出现 contentHeight 大于 容器顶部卷起来的长度 + 容器高度 的时候,说明当前元素是列表可视区域的最后一个元素,记为 endIndex 。

  • 完整代码:

  • <style>.list-container {overflow: auto;border: 1px solid black;height: 500px;}
    </style><body><!-- 外部容器用来固定列表容器的高度,同时生成滚动条 --><div class="list-container"><!-- 内部容器用来装元素,高度是所有元素高度的和 --><div class="list-container-inner"></div></div><script>/** --------- 一些基本变量 -------- */const itemHeight = 60const height = 500/** --------- 生成数据 -------- */const getRandomHeight = () => {// 返回 [60, 150] 之间的随机数return Math.floor(Math.random() * (150 - itemHeight + 1) + itemHeight)}const initData = () => {const data = []for(let i = 0; i < 15; i++) {data.push({content: `内容:${i}`, height: getRandomHeight(), color: i % 2 ? 'red' : 'yellow'})}return data}const data = initData()const cacheHeightMap = {}const outerContainer = document.querySelector('.list-container')const scrollCallback = () => {let contentHeight = 0let paddingTop = 0let upperHeight = 0let startIndexlet endIndexconst innerContainer = document.querySelector('.list-container-inner')const scrollTop = Math.max(outerContainer.scrollTop, 0)// 遍历所有的元素,获取当前元素的高度、列表总高度、startIndex、endIndexfor(let i = 0; i < data.length; i++) {// 初始化的时候因为元素还没有渲染,无法获取元素的高度// 所以用元素的最小高度itemHeight来进行计算,保证渲染的元素个数能占满列表const cacheHeight = cacheHeightMap[i]const usedHeight = cacheHeight === undefined ? itemHeight : cacheHeightcontentHeight += usedHeightif (contentHeight >= scrollTop && startIndex === undefined) {startIndex = ipaddingTop = contentHeight - usedHeight}if (contentHeight > scrollTop + height && endIndex === undefined) {endIndex = iupperHeight = contentHeight}}// 应对列表所有元素没有占满整个容器的情况if (endIndex === undefined) {endIndex = data.length - 1upperHeight = contentHeight}// 未渲染的元素的高度由padding-top和padding-bottom代替,保证滚动条位置正确// 这里如果把设置pading的操作放在渲染元素之后,部分浏览器滚动到最后一个元素时会有问题const paddingBottom = contentHeight - upperHeightinnerContainer.setAttribute('style', `padding-top: ${paddingTop}px; padding-bottom: ${paddingBottom}px`)// 从data取出要渲染的元素并渲染到容器中const viewData = data.slice(startIndex, endIndex + 1)innerContainer.innerHTML = ''const fragment = document.createDocumentFragment()for(let i = 0; i < viewData.length; i++) {const item = document.createElement('div')const itemData = viewData[i]item.innerHTML = itemData.contentitem.setAttribute('style', `height: ${itemData.height}px; background: ${itemData.color}`)fragment.appendChild(item)}innerContainer.appendChild(fragment)// 存储已经渲染出来的元素的高度,供后面使用const children = innerContainer.childrenlet flag = startIndexfor(const child of children) {cacheHeightMap[flag] = child.offsetHeightflag++}}// 首屏渲染scrollCallback()// 监听外部容器的滚动事件outerContainer.addEventListener('scroll', scrollCallback)</script>
    </body>
  • 在这里插入图片描述

相关文章:

用原生JS实现虚拟列表(IT枫斗者)

用原生JS实现虚拟列表 介绍 最近在开发需求的时候&#xff0c;有用到 Antd 的虚拟列表组件 rc-virtual-list &#xff0c;粗略地看了一下源码&#xff0c;于是萌生了自己写一个虚拟列表的想法。当一个列表需要渲染大量数据的时候是非常耗时的&#xff0c;而且在列表滚动的过程…...

FAT NTFS Ext3文件系统有什么区别

10 年前 FAT 文件系统还是常见的格式&#xff0c;而现在 Windows 上主要是 NTFS&#xff0c;Linux 上主要是Ext3、Ext4 文件系统。关于这块知识&#xff0c;一般资料只会从支持的磁盘大小、数据保护、文件名等各种维度帮你比较&#xff0c;但是最本质的内容却被一笔带过。它们最…...

信息收集思路

1、开发者注释 在网站前端代码中遗留的开发者注释 其中可能包含某些关键信息 ​ &#x1f4a1; 使用F12 、CtrlU 、view-source: 查看前端源码 ​ 3、Robots文件 爬虫协议&#xff0c;网站根目录存在的robots.txt文件&#xff0c;用于告知搜索引擎或爬虫哪些路径和页面不…...

Tauri应用开发(二):创建第一个Tauri应用

创建tauri应用 推荐参考官方文档&#xff1a;https://tauri.app/v1/guides/ 创建命令&#xff1a; npm create tauri-applatest&#x1f4a1;注意&#xff1a;请确保Node.js和Rust已经正确安装 在创建过程中&#xff0c;需要根据提示选择配置项。 主要配置有&#xff1a; 项目…...

自信裸辞:一晃 ,失业都3个月了.....

最近&#xff0c;找了很多软测行业的朋友聊天、吃饭 &#xff0c;了解了一些很意外的现状 。 我一直觉得他们技术非常不错&#xff0c;也走的测开/管理的路径&#xff1b;二三月份裸辞的&#xff0c;然后一直在找工作&#xff0c;现在还没找到工作 。 经过我的分析&#xff0…...

Python3 输入和输出

在Python 3中&#xff0c;你可以使用内置的函数来进行输入和输出操作。 输入&#xff08;Input&#xff09;&#xff1a; 要从用户那里获取输入&#xff0c;可以使用input()函数。input()函数会等待用户输入&#xff0c;并返回一个字符串。你可以将输入存储在一个变量中&#…...

Mybatis Plus 使用@TableLogic实现逻辑删除

文章目录 步骤1:修改数据库表添加deleted列步骤2:实体类添加属性步骤3:运行删除方法知识点1&#xff1a;TableLogic 接下来要讲解是删除中比较重要的一个操作&#xff0c;逻辑删除&#xff0c;先来分析下问题: 这是一个员工和其所签的合同表&#xff0c;关系是一个员工可以签多…...

2023/5/23总结

super关键字 super关键字的用法和this 关键字的用法相似 this:代表本类对象的引用&#xff08;this关键字指向调用该方法的对象一般我们是在当前类中使用this关键字&#xff0c;所以我们常说this代表本类对象的引用&#xff09;super:代表父类存储空间的标识(可以理解为父类对象…...

Squid代理服务器应用

在web架构中&#xff0c;用户一般进入负载均衡层&#xff0c;通过调度来访问web应用层&#xff0c;但是如果访问量太大&#xff0c;并发量较高&#xff0c;web应用层会吃不消&#xff0c;我们把静态资源、经常要访问的资源放入缓存&#xff0c;用户直接访问缓存层&#xff0c;加…...

网络编程中的sockfd是什么?

2023年5月22日&#xff0c;周一早上&#xff1a; 今天早上学习网络编程时遇到了sockfd这个变量&#xff0c;于是学习了一下&#xff0c;顺便写篇博客来记录自己的学习成功。 sockfd是什么意思&#xff1f; "sock"是socket的缩写。"fd"则是file descripto…...

如何利用Citespace和vosviewer既快又好地写出高质量的论文及快速锁定热点和重点文献进行可视化分析?

基于Citespace和vosviewer文献计量学可视化SCI论文高效写作方法 CiteSpace是什么&#xff1f; 简单来说&#xff0c;它一款通过将国内外文献进行可视化分析来帮助你了解一门学科前世今生的软件。 面对成千上万篇的文献&#xff0c;怎样才能快速锁定自己最感兴趣的主题及科学…...

(学习日记)AD学习 #1

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…...

缓存存在的问题

文章目录 缓存问题缓存穿透引入解决方案 缓存雪崩缓存击穿 缓存问题 使用缓存时常见的问题主要分为三个&#xff1a;缓存穿透 、缓存雪崩、缓存击穿。 下面对其进行一一学习 缓存穿透 引入 定义&#xff1a;缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在&#…...

ChatGPT 的 AskYourPDF 插件所需链接如何获取?

一、背景 目前 ChatGPT 主要有两款 PDF 对话插件&#xff0c;一个是 AskYourPDF 一个是 ChatWithPDF&#xff08;需 ChatGPT Plus&#xff09;&#xff0c;他们都可以实现给一个公共的PDF 链接&#xff0c;然后进行持续对话&#xff0c;对读论文&#xff0c;阅读 PDF 格式的文…...

基于自营配送模式的车辆路径规划设计与实现_kaic

摘要 近年来&#xff0c;随着我国消费水平逐渐提升&#xff0c;消费者在网上购物的频率也越来越高&#xff0c;电商发展速度迅猛&#xff0c;加大了物流配送的压力&#xff0c;促使物流企业以更大的运力&#xff0c;更短的时间将货物送达。在货品的运输过程中&#xff0c;成本居…...

动态规划-树形DP

树的重心 题目 链接&#xff1a;https://www.acwing.com/problem/content/848/ 给定一颗树&#xff0c;树中包含 n n n 个结点&#xff08;编号 1 ∼ n 1 \sim n 1∼n&#xff09;和 n − 1 n-1 n−1 条无向边。 请你找到树的重心&#xff0c;并输出将重心删除后&#x…...

多线程基础(二)CAS无锁优化/自旋锁/乐观锁、ABA问题

CAS &#xff08;Compare And Set&#xff09;比较并替换 上篇文章的锁问题解决&#xff0c;可以使用更高效的方法&#xff0c;使用AtomXXX类&#xff0c;AtomXXX类本身方法都是原子性的&#xff0c;但不能保证多个方法连续调用是原于性的。 import java.util.ArrayList; imp…...

记ABAC的落地实践

为什么使用ABAC 一般提到授权&#xff0c;我们就会想到角色&#xff08;role&#xff09;。什么样的用户拥有什么样的角色可以怎么操作什么样的资源&#xff0c;这是我们普遍使用的权限系统的模型。这里的角色实质上是包含了一组用户操作资源的规则集合。一旦角色被创建&#…...

【C++】C++11线程库 和 C++IO流

春风若有怜花意&#xff0c;可否许我再少年。 文章目录 一、C11线程库1.thread类介绍2.mutex互斥锁 和 CAS原子操作&#xff08;compare and set&#xff09;3.lock_guard和unique_lock4.两个线程交替打印&#xff0c;一个打印奇数&#xff0c;一个打印偶数&#xff08;线程同步…...

cpp11实现线程池(六)——线程池任务返回值类型Result实现

介绍 提交任务函数submitTask中返回的Result类型应该是用Result类包装当前的task&#xff0c;因为出函数之后task即如下形式&#xff1a;return Result(task); Result和Task都要互相持有对方的指针&#xff0c;Task要将任务执行结果通过Result::setVal(run()) 调用传给其对应…...

synchronized 学习

学习源&#xff1a; https://www.bilibili.com/video/BV1aJ411V763?spm_id_from333.788.videopod.episodes&vd_source32e1c41a9370911ab06d12fbc36c4ebc 1.应用场景 不超卖&#xff0c;也要考虑性能问题&#xff08;场景&#xff09; 2.常见面试问题&#xff1a; sync出…...

51c自动驾驶~合集58

我自己的原文哦~ https://blog.51cto.com/whaosoft/13967107 #CCA-Attention 全局池化局部保留&#xff0c;CCA-Attention为LLM长文本建模带来突破性进展 琶洲实验室、华南理工大学联合推出关键上下文感知注意力机制&#xff08;CCA-Attention&#xff09;&#xff0c;…...

无法与IP建立连接,未能下载VSCode服务器

如题&#xff0c;在远程连接服务器的时候突然遇到了这个提示。 查阅了一圈&#xff0c;发现是VSCode版本自动更新惹的祸&#xff01;&#xff01;&#xff01; 在VSCode的帮助->关于这里发现前几天VSCode自动更新了&#xff0c;我的版本号变成了1.100.3 才导致了远程连接出…...

多模态商品数据接口:融合图像、语音与文字的下一代商品详情体验

一、多模态商品数据接口的技术架构 &#xff08;一&#xff09;多模态数据融合引擎 跨模态语义对齐 通过Transformer架构实现图像、语音、文字的语义关联。例如&#xff0c;当用户上传一张“蓝色连衣裙”的图片时&#xff0c;接口可自动提取图像中的颜色&#xff08;RGB值&…...

WordPress插件:AI多语言写作与智能配图、免费AI模型、SEO文章生成

厌倦手动写WordPress文章&#xff1f;AI自动生成&#xff0c;效率提升10倍&#xff01; 支持多语言、自动配图、定时发布&#xff0c;让内容创作更轻松&#xff01; AI内容生成 → 不想每天写文章&#xff1f;AI一键生成高质量内容&#xff01;多语言支持 → 跨境电商必备&am…...

【HTML-16】深入理解HTML中的块元素与行内元素

HTML元素根据其显示特性可以分为两大类&#xff1a;块元素(Block-level Elements)和行内元素(Inline Elements)。理解这两者的区别对于构建良好的网页布局至关重要。本文将全面解析这两种元素的特性、区别以及实际应用场景。 1. 块元素(Block-level Elements) 1.1 基本特性 …...

实现弹窗随键盘上移居中

实现弹窗随键盘上移的核心思路 在Android中&#xff0c;可以通过监听键盘的显示和隐藏事件&#xff0c;动态调整弹窗的位置。关键点在于获取键盘高度&#xff0c;并计算剩余屏幕空间以重新定位弹窗。 // 在Activity或Fragment中设置键盘监听 val rootView findViewById<V…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

AI书签管理工具开发全记录(十九):嵌入资源处理

1.前言 &#x1f4dd; 在上一篇文章中&#xff0c;我们完成了书签的导入导出功能。本篇文章我们研究如何处理嵌入资源&#xff0c;方便后续将资源打包到一个可执行文件中。 2.embed介绍 &#x1f3af; Go 1.16 引入了革命性的 embed 包&#xff0c;彻底改变了静态资源管理的…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...