【js】无限虚拟列表的原理及实现

【js】无限虚拟列表的原理及实现虚拟列表是长列表按需显示思路的一种实现 即虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术

大家好,欢迎来到IT知识分享网。

什么是虚拟列表

虚拟列表是长列表按需显示思路的一种实现,即虚拟列表是一种根据滚动容器元素的可视区域来渲染长列表数据中某一个部分数据的技术。

简而言之,虚拟列表指的就是「可视区域渲染」的列表。有三个概念需要了解一下:

视口容器元素: 定义固定宽高的元素,该区域限制无限虚拟列表的可视区域大小
可滚动区域元素: 宽高为父元素的100%,纵向超出可滚动
内容区域元素: 宽度100%,高度auto,用于呈放渲染的部分列表项,撑开可滚动区域

在这里插入图片描述

实现思路

实现虚拟列表就是,当用户滚动时,动态改变可视区域内的渲染内容

滚动时 =》

  • 监听可滚动区域滚动事件

变化 =》

  • 内容区域渲染的列表数据变化
  • 内容区域一直显示在视口上
  • 滚动区域的高度增加

在这里插入图片描述

具体实现

想明白思路之后,根据思路,一步步进行

首先创建四个html元素

分别定义类名为:container、list_scroll、list、item,结构如下

<div class="container"> <div class="list_scroll"> <div class="list"> <div class="item">1</div> </div> </div> </div> 

通过类名定义样式

 /* 最外层容器,宽高固定列表视口大小 */ .container{ 
    width:500px; height: 800px; border: 1px solid #f80c0c; margin: auto; /* 居中 */ } /* 可滚动容器,占最外层容器宽高100% 能被显示的列表撑开 */ .list_scroll{ 
    width: 100%; height: 100%; overflow: auto; /* 超出滚动 */ background-color: antiquewhite; } /* 虚拟列表容器,用于展示长列表位于视口区域的部分项 */ .list{ 
    width: 90%; margin: auto; /* 居中 */ } /* 子项 */ .item{ 
    width: 100%; border: 1px solid #000; box-sizing: border-box; display: flex; justify-content: center; align-items: center; } 

创建好元素之后,开始写js逻辑实现

准备操作:

需要两个数组:源数据、渲染列表数据和视口展示列表的长度,即可展示的最大数量
可展示的最大数量: 可通过 “视口容器的高度 / item的高度” 获取,默认item高度固定
渲染列表数据:通过对源数据进行切割获取,所以还需要知道切割数组的开始位置、结束位置

// 获取容器和列表元素 const listScroll = document.querySelector('.list_scroll') const list = document.querySelector('.list') // 源数据 const dataSource = [] // 渲染数据=> 通过定义首位index截取源数据 let renderData = [] // item的高度 const itemHeight = 50 // listScroll容器能够显示的最大数量 // +2 撑开listScroll容器使其具有滚动条 const maxCount = Math.floor(listScroll.clientHeight / itemHeight) + 2 // 开始位置索引 let startIndex = 0 // 结束位置索引 let endIndex = 0 

获取源数据

// 源数据 function GetData () { 
    for (let i = 0; i < 200; i++) { 
    dataSource.push(i) } } 

计算开始位置和结束位置

// 计算开始位置和结束位置索引 function ComputePointerPosition () { 
    const end = startIndex + maxCount endIndex = dataSource[end] ? end : dataSource.length } 

根据开始位置和结束位置,截取渲染数据

// 截取渲染数据 function GetRenderData () { 
    renderData = dataSource.slice(startIndex, endIndex) } 

万事具备,只欠东风,开始渲染到页面

// 渲染 function Render () { 
    // 计算开始和结束位置 ComputePointerPosition() // 获取数据 GetRenderData() // 将截取的渲染数据生成动态的item元素,填充到list内容元素 list.innerHTML = renderData.map(item => `<div class="item" style="height: ${ 
     itemHeight}px">${ 
     item}</div>`).join('') } 

监听可滚动区域的滚动事件

// 监听滚动事件 listScroll.addEventListener('scroll', ScrollHandle) // 监听listOut滚动事件 function ScrollHandle () { 
    // 更新开始位置索引:滚动的距离 / 每个元素的高度 startIndex = Math.floor(listScroll.scrollTop / itemHeight) // 更新位置,重新渲染 Render() } 

运行
感觉怪怪的,并且没一会就到底了
在这里插入图片描述
仔细观察你就会发现,这有2个问题


  1. 当第一个item滑出可视区域之后,右侧的dom结构渲染是正确的第一个item变为1,但是页面上看到是第一个是2;当再次向下滚动一个元素之后,右侧dom第一个item为2,但是页面上看到的第一个确是4
  2. 可滚动区域的高度并没有随着滚动一直增加,没几下就触底了,没有办法再继续监听了,也就没有办法继续更新数据了

每次向下滚动一个元素,列表会向上移动一个元素的位置,startIndex表示已经上移到的元素的个数,itemHeight表示每个元素的高度,所以我们将ScrollHandle事件改成如下

// 监听listOut滚动事件 function ScrollHandle () { 
    // 更新位置,重新渲染 Render() // 测试发现每次向下滚动一个元素,列表会向上移动一个元素的位置,所以增加transform属性,使列表位置向下移动一个元素的位置 // startIndex表示已经上移到的元素的个数,itemHeight表示每个元素的高度 list.style.transform = `translateY(${ 
     startIndex * itemHeight}px)` } 

优化

// 记录到的位置索引 let pointerIndex = 0 // 监听listOut滚动事件 function ScrollHandle () { 
    // 更新开始位置索引:滚动的距离 / 每个元素的高度 startIndex = Math.floor(listScroll.scrollTop / itemHeight) // 一致不做渲染 if (pointerIndex === startIndex) return pointerIndex = startIndex // 更新位置,重新渲染 Render() // 测试发现每次向下滚动一个元素,列表会向上移动一个元素的位置,所以增加transform属性,使列表位置向下移动一个元素的位置 // startIndex表示已经上移到的元素的个数,itemHeight表示每个元素的高度 list.style.transform = `translateY(${ 
     startIndex * itemHeight}px)` } 

在这里插入图片描述
加载更多

到此,虚拟列表的实现已经完成,源数据是长度为200的长列表。我们可以判断是否到底,来加载更多,可通过已加载的数组的总长度 - 开始位置是否 小于 可展示的最大数量,此时需要加载更多数据

// 监听listOut滚动事件 function ScrollHandle () { 
    // 更新开始位置索引:滚动的距离 / 每个元素的高度 startIndex = Math.floor(listScroll.scrollTop / itemHeight) if (pointerIndex === startIndex) return pointerIndex = startIndex // 更新位置,重新渲染 Render() if (dataSource.length - startIndex >= maxCount) { 
    // 测试发现每次向下滚动一个元素,列表会向上移动一个元素的位置,所以增加transform属性,使列表位置向下移动一个元素的位置 // startIndex表示已经上移到的元素的个数,itemHeight表示每个元素的高度 list.style.transform = `translateY(${ 
     startIndex * itemHeight}px)` } else { 
    // 滑动到底部 加载增更多数据 GetData() } } 

完整代码

// 获取容器和列表元素 const listScroll = document.querySelector('.list_scroll') const list = document.querySelector('.list') // 源数据 const dataSource = [] // 渲染数据=> 通过定义首位index截取源数据 let renderData = [] // item的高度 const itemHeight = 50 // listScroll容器能够显示的最大数量 // +2 撑开listScroll容器使其具有滚动条 const maxCount = Math.floor(listScroll.clientHeight / itemHeight) + 2 // 开始位置索引 let startIndex = 0 // 结束位置索引 let endIndex = 0 // 源数据 function GetData () { 
    for (let i = 0; i < 200; i++) { 
    dataSource.push(i) } } // 计算开始位置和结束位置索引 function ComputePointerPosition () { 
    const end = startIndex + maxCount endIndex = dataSource[end] ? end : dataSource.length } // 截取渲染数据 function GetRenderData () { 
    renderData = dataSource.slice(startIndex, endIndex) } // 渲染 function Render () { 
    // 计算开始和结束位置 ComputePointerPosition() // 获取数据 GetRenderData() // 将截取的渲染数据生成动态的item元素,填充到list内容元素 list.innerHTML = renderData.map(item => `<div class="item" style="height: ${ 
     itemHeight}px">${ 
     item}</div>`).join('') } // 记录到的位置索引 let pointerIndex = 0 // 监听listOut滚动事件 function ScrollHandle () { 
    // 更新开始位置索引:滚动的距离 / 每个元素的高度 startIndex = Math.floor(listScroll.scrollTop / itemHeight) if (pointerIndex === startIndex) return pointerIndex = startIndex // 更新位置,重新渲染 Render() if (dataSource.length - startIndex >= maxCount) { 
    // 测试发现每次向下滚动一个元素,列表会向上移动一个元素的位置,所以增加transform属性,使列表位置向下移动一个元素的位置 // startIndex表示已经上移到的元素的个数,itemHeight表示每个元素的高度 list.style.transform = `translateY(${ 
     startIndex * itemHeight}px)` } else { 
    // 滑动到底部 加载增更多数据 GetData() } } function init () { 
    // 获取数据 GetData() Render() // 监听滚动事件 listScroll.addEventListener('scroll', ScrollHandle) } init() 

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/113352.html

(0)
上一篇 2025-12-15 10:00
下一篇 2025-12-15 10:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信