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

JavaScript原生实现简单虚拟列表(列表不定高)

本文首发在我的个人博客上:JavaScript原生实现简单虚拟列表(列表不定高)https://www.brandhuang.com/article/1745637125513

前言

之前实现了一个定高版本的虚拟列表,今天在定高版本的基础上稍作调整,来实现不定高版本,之前的版本请跳转对应文章查看:JavaScript原生实现简单虚拟列表(固定高度) https://www.brandhuang.com/article/1616725433946

先说结论

实现不定高的原理就是:每次把内容渲染到页面后,都去重新获取一次 item 的实际高度,然后再执行一次渲染

最开始想用 createDocumentFragment 创建一个虚拟的文档节点,先将内容渲染到这个虚拟的文档节点中,最后从虚拟文档节点中获取 item 的实际高度,最后,一次插入到真实DOM中。结果发现,在虚拟的文档节点中时拿不到 item 的实际高度的,所以才有了下面的实现方式。

完整代码

不定的高版本代码如下:

html 和 css 与定高版本相比,未做任何调整

    <div class="container"><div class="zhanwei"></div></div><style> .container {border: 1px solid #eee;height: 300px;width: 300px;overflow: auto;position: relative;box-sizing: border-box;}.zhanwei {position: relative;}.item {position: absolute;top: 0;min-height: 50px;width: 100%;border: 1px solid #eee;will-change: transform; box-sizing: border-box;}.item:nth-of-type(odd) {background: #00ccff;}.item:nth-of-type(even) {background: #ffcc00;}</style>

js 代码如下

和定高版本相比,就两处改动,请查看代码中的 变化一 和 变化二

// 不固定高度版本let container = document.querySelector('.container');let zhanwei = document.querySelector('.zhanwei');let itemList = []; // 假设有10000条数据for (let i = 0; i < 10000; i++) {// 生成10000条数据itemList.push({index: i,content: `Item ${i} - ${"Hello world!".repeat(Math.floor(Math.random() * 10))}`});};let buffer = 5; // 多渲染几条,避免滚动看着异常let itemHeight = 50; // 每条数据的一个默认最小高度let heights = new Map();// 记录渲染的每个 item 的高度,为不定高版本做准备let offsets = new Map(); // 记录每个 item 的偏移量,即每个item距离顶部的距离let rendered = new Map(); // 存储已渲染的数据// 更新偏移量, 根据item高度,计算 zhanwei 元素的高度,好让container出现滚动条function updateOffsets() {let offset = 0for (let i = 0; i < itemList.length; i++) {let h = heights.get(i) ?? itemHeight; // ?? 是空值合并运算符,当左边为null或者undefined时使用右边值,和三元运算符相比,排除了 0 的干扰offsets[i] = offset;offset += h + 5; // 加上了5个像素的间距}zhanwei.style.height = offset + 'px';}// 变化一:创建一个重新渲染函数function rerender(item, i) {let height = item.getBoundingClientRect().heightif (heights.get(itemList[i].index) !== height) {heights.set(itemList[i].index, height)updateOffsets()render()}}// 渲染数据function render() {let scrollTop = container.scrollTop;let viewHeight = container.clientHeight;let start = 0; // 查找视口第一个item的索引while (start < itemList.length && offsets[start + 1] < scrollTop) {start++;}let end = start ;// 查找视口最后一个item的索引while (end < itemList.length && offsets[end] < scrollTop + viewHeight) {end++;}start = Math.max(0, start - buffer);end = Math.min(itemList.length, end + buffer);let nextRendered = new Map(); // 当前需要渲染的数据for (let i = start; i < end; i++) {if (!rendered.has(i)) {let item = document.createElement('div')item.className = 'item'item.style.transform = `translateY(${offsets[itemList[i].index] + 'px'})`item.textContent = itemList[i].contentcontainer.appendChild(item)rendered.set(i, item)// 变化二:向页面插入数据后执行一次重新渲染rerender(item, i)}nextRendered.set(i, rendered.get(i))}// 不可见的区域 移除for (const [i, el] of rendered.entries()) {if (!nextRendered.has(i)) {container.removeChild(el);}}// 更新 renderedrendered.clear();for (const [i, el] of nextRendered.entries()) {rendered.set(i, el);}}container.addEventListener("scroll", render);updateOffsets()render()

如果你有更好的实现方案,欢迎留言、贴代码交流。

感谢你的阅读 ❤️

http://www.xdnf.cn/news/164143.html

相关文章:

  • 【Agent python实战】ReAct 与 Plan-and-Execute 的融合之道_基于DeepSeek api
  • 快速上手c语言
  • 栈与堆的演示
  • C++ 为什么建议类模板定义在头文件中,而不定义在源文件中
  • 对卡尔曼滤波的理解和简单示例实现
  • 数据库原理(1)
  • N字形上升形态选股代码如何编写?
  • 平面连杆机构(上)
  • 定制一款国密浏览器(11):SM2算法的椭圆曲线参数定义
  • 4月25日日记(补)
  • 6.Geometric Intersection (几何求交)- Preliminary
  • 用高德API提取广州地铁线路(shp、excel)
  • Docker Compose--在Ubuntu中安装Docker compose
  • Java 异常处理全解析:从基础到自定义异常的实战指南
  • Java社区门诊系统源码 SaaS医院门诊系统源码 可上线运营
  • 深入理解JavaScript异步编程:从回调地狱到Promise/Async优雅解决方案
  • Eigen核心矩阵/向量类 (Matrix, Vector, Array)
  • 循环神经网络RNN---LSTM
  • 函数递归之青蛙跳台阶+汉诺塔
  • 网络原理 - 8
  • 某海关某署 【瑞数6】逆向分析
  • 矩阵系统私信功能开发技术实践,支持OEM
  • Eigen的主要类及其功能
  • ACPs:面向智能体互联网的智能体协作协议体系
  • 经典反转结构——案例分析
  • 《算法竞赛进阶指南》0x20章目录
  • 57常用控件_QLineEdit的属性
  • 使用css修饰网页元素
  • 聚合分销系统开发:短剧小说外卖网盘电商cpscpa系统
  • PCL点云处理之基于FPFH特征的SAC-IA全局配准算法 (二百四十六)