likes
comments
collection
share

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

作者站长头像
站长
· 阅读数 45

写在最前

引出

作为一名前端工程师,大数据量的展示是我们日常的面临的挑战之一

大数据量展示的场景当中我们碰到比较多的就是长列表

一般我们处理长列表在大数据量下的展示就3种方案

  • 分页
  • 懒加载(滚动加载或者说无限滚动)
  • 虚拟列表

我们今天要说的就是虚拟列表

什么是虚拟列表?

引出痛点

首先不使用没有虚拟列表,我们用React先渲染10w条数据

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

统计一下时间

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

用虚拟列表来渲染

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

统计一下时间

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

对比一下二者的时间,很明显虚拟列表,有着很明显的优势。

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

Ps:这里每个item还只是简单的结构,随着复杂度的增加,优势应该会更加明显。

虚拟列表的理解

下面看一张原理图

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

再结合滚动时候的样子

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

虚拟列表的意思就呼之欲出了,就是只渲染指定视口内的可见部分,其余部分以虚拟(内存)的形式保存,等待需要时再渲染

来实现一个简单的虚拟列表

虚拟列表有好多种,大致上有2种,定高,不定高的

这里只讲定高的。目的在于理解最简单的原理

定义一下dom结构

窗口的div包裹,设置overflow:auto 和 监听滚动事件

内部内容区域利用样式margin-topheight来撑起整个高度,使得窗口div容器能顺利滚动

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

然后理一下组件的props

interface ListProps  {
    data:any[] // 渲染的数据
    itemHeight?:number // 每个item的高度
    containerHeight?:number // 外部窗口的高度
    bufferIndex?:number  // 缓冲数量(这里暂不实现)
}

最后写一下代码

// VirtualList.tsx
const VirtualList: React.FC<ListProps> = ({data,itemHeight:inputHeight,containerHeight:cHeight}) => {
    const [scrollTop, setScrollTop] = useState(0);
  
    const handleScroll = (event:React.UIEvent<HTMLDivElement>) => {
      setScrollTop(event.currentTarget.scrollTop);
    };
  
    const itemHeight = inputHeight ? inputHeight : 50;
    const containerHeight = cHeight ? cHeight :window.innerHeight
    const totalItems = data.length;
    const visibleItems = Math.floor(containerHeight / itemHeight);
    const startIndex = Math.floor(scrollTop / itemHeight);
    const endIndex = startIndex + visibleItems;
  
    const visibleData = data.slice(startIndex, endIndex);
    
    return (
      <div
        style={{ height: `${visibleItems * itemHeight}px`,overflowY: "auto" }}
        onScroll={handleScroll}
      >
        <div style={{ height: `${totalItems * itemHeight - startIndex*itemHeight}px`,marginTop:`${startIndex*itemHeight}px` }}>
          {visibleData.map((item, index) => (
            <div key={index} style={{ height: `${itemHeight}px`,backgroundColor: isOdd(index)?"lightpink":"lightgray"}}>
              {item.content}
            </div>
          ))}
        </div>
      </div>
    );
};

ok,到这里一个最简单的虚拟列表就实现完毕了~

那么有什么问题呢?

数据量大时,当我们快速去拖动滚动条,会出现滚动条拖动不卡顿,但列表的显示短暂白屏的问题

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

加入缓冲区

意思是除了窗口区域之外,多渲染一些,具体的逻辑可以去参考react-window

可以看到加入缓冲区之后,白屏的效果得到了缓解

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

其实不难发现,还是会有白屏的问题。

还有1个优化方法

参考骨架屏的思路,提前将不显示的区域全部渲染出骨架,避免白屏效果

白屏原因分析

在这里说下我理解的白屏原因

首先梳理一下用户触发滚动的过程:

  • 用户的滚动从browser process经由render 进程的合成器线程Compositor Thread转发给渲染主线程

  • 渲染主线程Main Thread触发相关的用户输入事件处理加入到宏任务队列当中

  • 然后在事件循环的每个迭代当中,渲染主线程会取出1个宏任务,压入执行栈给v8引擎去执行,之后依次清空执行过程当中产生的微任务队列

  • 接着主线程会进行页面样式的重新计算页面布局的重新计算

  • 交给合成器线程Compositor Thread分层绘制合成、生成图层和分块

  • 最后将这些图块交给gpu线程去绘制到屏幕上

过程如下图

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

可以看到 合成器线程 控制着 屏幕刷新率事件、 用户的输入,和frame的输出

在这里推荐Chrome高级工程师Paul Lewis大佬写的The Anatomy of a Frame

里面有当提到滚动的时候,合成器线程尝试直接转换成屏幕的移动。通过更新图层位置和直接传递帧给gpu通过gpu线程

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出 但如果有事件处理器的话,还是需要主线程去干活,那么为啥滚动的时候流畅,只是白屏呢?

我们可以看看Chromium的设计文档

里面有一篇文章GPU Accelerated Compositing in Chrome 介绍了GPU加速合成机制,里面明确提到了对滚动性能的优化

输入和输出都在合成器线程的控制下,合成器线程可以保证用户输入的视觉响应。

touch事件的滚动可以被js当中的preventDefault()取消,而滚动事件不行。

因为滚动事件会异步传递给JavaScript,合成器线程可以立即开始滚动,而不管主线程是否立即处理滚动事件。

性能优化之虚拟列表及白屏原因浅析写在最前 看官们好,我叫JetTsang,之前都是在掘金潜水来着,现在偶尔做一些内容输出

想必看到这里,白屏的原因昭然若揭:

合成器线程控制了用户的输入,以及图像的输出权,所以有优先响应用户输入(比如滚动)的能力,它可以先响应滚动,而无需等待渲染主线程去调用v8执行事件处理函数及后续的操作的执行

在项目当中使用虚拟列表

生产环境下一般要考虑健壮性强的库,在这里推荐一些 Vue: vueuse vue-virtual-scroller React: ahooks react-window

挖坑

  • 不定高的虚拟列表实现

  • 实现一个虚拟列表的hooks(支持定高和不定高)

写在最后

如果你觉得本文对你有所帮助,不妨给我一个点赞和收藏,这将是对我最大的鼓励。

转载自:https://juejin.cn/post/7254786711719608380
评论
请登录