Skip to content

分片渲染算法概述

分片渲染算法概述

需求

  1. 渲染长表格:如果前端渲染很长的页面,或者很宽度的页面,把全部 dom 都渲染出来,浏览器负担很重,可能卡死;例子:渲染二维表格(10000行,每一行20列);

  2. 渲染图片库:如果DOM包含图片,渲染时发出网络请求,会短时间发送大量请求,不利于界面。例子:渲染一个照片库,可能有10000张照片,同时发出网络请求。

思路

只渲染屏幕区域可见的小部分,不可见的部分渲染一个空的 DIV 占位。

当界面滚动时,重新计算屏幕显示的部分,显示屏幕可见区域和周边若干行(例如100行),其他的9000行还是使用 div 占位,动态改变高度即可。

垂直方向,计算出 startIndex, endIndex,startIndex - interval 到 endIndex + interval 就是需要渲染的部分。上面 (0 —— startIndex - interval )x row_height 就是上面空白 div 的高度,下面 (endIndex + interval —— rows.length x row_height 就是下面空白的高度。

水平方向类似,计算 leftIndex 和 rightIndex 然后分片渲染,同上。

如果是图片库,那么 dom 节点没有那么多,主要问题是网络请求,那么可以根据滚动的位置,计算出图片是否在屏幕显示区域。如果在显示的区域,直接设置 img-src 渲染真实的图片(或者缩略图),如果不在屏幕可见区域内部,直接把 src 设置成 '' 这样就不发出网络请求(或者设置一个 # 等)

代码示例

// All rows
let rows = new Array(1000).fill('test');

// 行高是100px
let rowHeight = 100;

// 显示可见区域以及上下的 10行
let interval = 10;

// 获取当前滚动的位置
let scrollTop = getScrollTop();
let clientHeight = getClientHeight();

// 需要考虑边界和小数等细节
let startIndex = Math.max(0, scrollTop - interval * rowHeight) / rowHeight;

let endIndex = Math.min(rows.length, (scrollTop + clientHeight) / rowHeight + inerval)

// react 状态驱动,但是每次滚动不需要都重新渲染
// 实际上这里计算绝对值
if (startIndex - oldStartIndex > 5) {
    this.setState({ startIndex });
}
if (endIndex - oldEndIndex > 5) {
    this.setState({ oldIndex });
}

// render 阶段

let topHeight = startIndex * rowHeight;
let bottomHeight = (rows.length - endIndex) * rowHeight;

render () {
    return(
        <div className="container" onScroll={this.onScroll} ref={}>
            <div style={{height: topHeight, width: 100%}}></div>

            {rows.slice(startIndex, endIndex).map(item => {
                return <span></span>;
            })}

            <div style={{height: bottomHeight}}></div>
        <div/>
    );
}

性能分析

  1. 滚动时,需要计算界面滚动的位置,所以频繁滚动性能不好,根据实际情况,加一个节流函数,避免太快的滚动界面触发函数
  2. 渲染图片时,普通小图片使用缩略图,点击后展示高清大图
  3. 请求时,最好也可以分片请求(设置 start end 参数请求,避免一次性请求全部的行)然后界面滚动时,当滚动到底部时,可以触发下一个请求加载更多的行
  4. 不同浏览器性能不同,可能火狐在100万个DOM节点就卡死了,谷歌在300-400万个DOM就卡死了,需要根据实际情况决定 interval 等参数

Last update: November 5, 2023