「虚拟滚动」也没那么难嘛

最近在做一个React Native项目:需要滑动展示成千上万的图片。直接堆上去性能爆炸,又没有好用的虚拟滚动基建,于是自己动手,造一个!(为了方便展示,写了一个 web 版的 demo)

在线体验:leigithub1024.github.io/VirtualScro...

仓库地址:github.com/LeiGitHub10...

老方案的问题

这个项目以前只需要展示几百张图片,所以不需要做什么优化,一次请求好,直接堆上去就好了。但新版本需要展示的数据可能成千上万,如果一次请求全量数据,需要几分钟,滑动起来也会很卡。

尽管 React Native 提供了如FlatList、SectionList等虚拟滚动组件,但它们不完全符合复杂的业务需求且灵活性不足。因此,我决定自己动手,丰衣足食!

核心功能 - 动态加载

只渲染可见部分这是虚拟滚动的核心思想,用到谁就请求谁,渲染谁。其他一切工作都是围绕这一点出发的。

一种简单的解决方案是给每个元素添加一个监听器,如果在屏幕范围内就展示,不在就隐藏。但这样过多的监听器也会带来大量开销。更经济的做法是只监听父容器,根据父容器的当前位置,计算哪些需要展示,哪些不需要。代码如下:

js 复制代码
    //计算左右边界
    const container = document.querySelector('.slide-container');
    const scrollLeft = container.scrollLeft;      
    const containerWidth = container.clientWidth;
    const firstVisibleIndex = Math.floor(scrollLeft / 100 ) ; // 每个项的宽度是 100px
    const lastVisibleIndex = Math.ceil((scrollLeft + containerWidth) / 100) ;
    //挨个判断需不需要展示
    document.querySelectorAll('.item-placeholder').forEach((element, index) => {
        if (index >= firstVisibleIndex && index <= lastVisibleIndex) {
          if (!element.querySelector('img')) { // 检查是否已加载图片
            let img = document.createElement('img');
            img.src = `https://s11.ax1x.com/2023/01/30/pSdWF7F.png`; 
            img.style.height = "100%";
            img.style.objectFit = "cover";
            element.appendChild(img);
          }
          element.classList.add('item-active');
        } else {
          element.classList.remove('item-active');
          let img = element.querySelector('img');
          if (img) { // 如果存在图片则移除,防止占用过多内存
            element.removeChild(img);
          }
        }
      });

优化 - 节流

滑动是很灵敏的事件,为了进一步提高性能,需要加上节流机制。同时为了保证滑动停止时数据能够及时更新,需要监听滑动停止事件,并额外触发一次更新。

js 复制代码
   const handleScroll = throttle(function() {
      freshData();
      if (scrollTimeout !== null) {clearTimeout(scrollTimeout)}
      scrollTimeout = setTimeout(handleScrollEnd, 500); // 500ms后无新的滑动事件,认为滑动结束
    }, 250); // 250ms 的节流,防止滚动时频繁触发

优化 - 缓冲区

简单地扩大缓冲区,可以显著改善用户的滚动体验,减少加载的等待时间。

js 复制代码
   const buffer = 20; // 增加20个元素的 buffer
   const firstVisibleIndex = Math.floor(scrollLeft / 100 ) - buffer; // 每个项的宽度是 100px
   const lastVisibleIndex = Math.ceil((scrollLeft + containerWidth) / 100) + buffer;

demo 效果展示

做完上述工作,我们便掌握了虚拟滚动最关键的部分。我用 M1 芯片的 Mac 测试,10 万条以内的数据很流畅,即使有 100 万数据,也不至于卡死。

在线体验:leigithub1024.github.io/VirtualScro...

仓库地址:github.com/LeiGitHub10...

参考

相关推荐
Smile_Gently1 小时前
前端:最简单封装nmp插件(组件)过程。
前端·javascript·vue.js·elementui·vue
luckycoke8 小时前
小程序立体轮播
前端·css·小程序
一 乐8 小时前
高校体育场管理系统系统|体育场管理系统小程序设计与实现(源码+数据库+文档)
前端·javascript·数据库·spring boot·高校体育馆系统
懒羊羊我小弟8 小时前
常用Webpack Loader汇总介绍
前端·webpack·node.js
祈澈菇凉8 小时前
ES6模块的异步加载是如何实现的?
前端·javascript·es6
我爱学习_zwj9 小时前
4.从零开始学会Vue--{{组件通信}}
前端·javascript·vue.js·笔记·前端框架
顾比魁9 小时前
XSS盲打:当攻击者“盲狙”管理员
前端·网络安全·xss
黑客老李9 小时前
新手小白如何挖掘cnvd通用漏洞之存储xss漏洞(利用xss钓鱼)
java·运维·服务器·前端·xss
晚风予星9 小时前
简记|LogicFlow自定义BPMN元素节点
前端
Json____9 小时前
使用html css js 开发一个 教育机构前端静态网站模板
前端·css·html·js·前端学习·企业站·教育机构网站