分片渲染算法概述
分片渲染算法概述¶
统计信息:字数 3341 阅读7分钟
需求¶
-
渲染长表格:如果前端渲染很长的页面,或者很宽度的页面,把全部 dom 都渲染出来,浏览器负担很重,可能卡死;例子:渲染二维表格(10000行,每一行20列);
-
渲染图片库:如果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/>
);
}
性能分析¶
- 滚动时,需要计算界面滚动的位置,所以频繁滚动性能不好,根据实际情况,加一个节流函数,避免太快的滚动界面触发函数
- 渲染图片时,普通小图片使用缩略图,点击后展示高清大图
- 请求时,最好也可以分片请求(设置 start end 参数请求,避免一次性请求全部的行)然后界面滚动时,当滚动到底部时,可以触发下一个请求加载更多的行
- 不同浏览器性能不同,可能火狐在100万个DOM节点就卡死了,谷歌在300-400万个DOM就卡死了,需要根据实际情况决定 interval 等参数
Last update:
November 9, 2024