普通视图

发现新文章,点击刷新页面。
今天 — 2025年1月18日首页

【虚拟列表·终章】不定高度+动态图片加载,十万条数据流畅渲染全攻略!

作者 前端大卫
2025年1月17日 18:37

大家好,我是 前端大卫

虚拟列表-示例.gif

在线 Demo 地址: codesandbox.io/p/devbox/ad…

今天是 虚拟列表 系列的终章,我将带大家深入探讨 不定高度列表项 的处理方式。如果你还没有看过前两篇内容,可以先点击下面的链接:

解决方案的优势

相较于市面上其他虚拟列表实现,我的这个方案具备以下优势:

  1. 高效索引查找
    根据滚动方向精准定位起始和结束索引,显著提升性能。
  2. 创新高度调整机制
    无需依赖传统二分查找算法,而是通过记录高度调整值,保证列表项的连续性。
  3. 动态监听高度变化
    利用 ResizeObserver 实时监听可视区域和整体列表高度的变化,确保数据准确。

如果你的项目不支持 ResizeObserver,欢迎在评论区留言,我会单独出一篇文章讲解如何用其他技术解决监听问题。

接下来,我会通过实例,从 简单到复杂 手把手讲解解决方案,并分享一些重要注意事项。

核心实现步骤

1. 初始化数据结构

假设以下场景:

  • 每项预估高度为 100
  • 可视区域高度为 450

初始数据结构如下:

[
  { "top": 0, "bottom": 100, "height": 100 },
  { "top": 100, "bottom": 200, "height": 100 },
  { "top": 200, "bottom": 300, "height": 100 },
  ...
]

预估高度.png

可以发现:

  • 每项的 top 值为前一项的 bottom 值。
  • 每项的 bottom 值为自身 top + height

2. 精确查找索引

根据滚动高度,确定起始和结束索引的公式为:

if (scrollTop >= item.top && scrollTop <= item.bottom) {
   // 找到对应索引
}

注意:
列表项之间必须保持连续,否则会出现无法匹配的情况。例如,如果滚动高度为 140,而列表项如下:

[
  { "top": 0, "bottom": 100 },
  { "top": 200, "bottom": 300 },
  { "top": 300, "bottom": 400 }
]

此时无法找到对应的索引,导致无法渲染虚拟列表。

3. 预估高度的作用

预估高度用于初始渲染,后续会根据实际高度进行调整。例如:
滚动高度为 0,起始索引为 0,结束索引为 4,渲染如下:

预估高度.png

4. 高度修正

渲染完成后,各列表项的实际高度可能不同。通过 ResizeObserver,我们可以动态监听每项高度并修正:

  • 如果列表项 高度较低:结束索引会增加,例如从 4 变为 7

高度较低修正.png

  • 如果列表项 高度较高:结束索引会减少,例如从 4 变为 3

高度较高修正.png

5. 确保列表项连续性

这是实现的核心难点。未渲染的列表项需要与已渲染项保持连续,以下分两种情况讨论:

情况 1:高度连续

如果下一项的 top 大于上一项的 bottom,简单赋值即可:

if (nextItem.top >= lastItem.bottom) {
  nextItem.top = lastItem.bottom;
}

高度连续.png

情况 2:高度不连续

如果下一项的 top 小于上一项的 bottom,需要记录调整值:

const heightAdjustment = lastItem.bottom - nextItem.top;

高度不连续.png

无需逐项更新所有列表项,只需在需要时应用调整值即可。

6. 滚动方向的优化查找

根据 newScrollTopprevScrollTop,判断滚动方向:

  • 向下滚动:新起始索引在旧索引下方。
  • 向上滚动:新起始索引在旧索引上方。

利用方向判断,比二分查找效率更高。

7. 动态调整后的处理

当某项被删除或高度变化时,确保页面流畅性:

  • 利用 uid 唯一标识复用旧列表项的数据。
  • 根据规则修正:
    • top = previous.bottom
    • bottom = top + height

结语

代码细节可以查看我的 GitHub 项目,希望大家点个 ⭐ 支持!

GitHub 源码地址:
github.com/feutopia/fe…

如果你对虚拟列表有其他问题或建议,欢迎留言讨论!

最后

点赞👍 + 关注➕ + 收藏❤️ = 学会了🎉。

更多优质内容关注公众号,@前端大卫。

❌
❌