一、先理解虚拟滚动的原理
虚拟滚动其实就是综合数据分页和无限滚动的方法,在有限的视口中只渲染我们所能看到的数据,超出视口之外的数据就不进行渲染,可以通过计算可视范围内的但单元格,保证每一次滚动渲染的DOM元素都是可以控制的,不会担心像数据分页一样一次性渲染过多,也不会发生像无限滚动方案那样会存在数据堆积,也就是按需渲染。 大致如图(网上找的):
二、代码实现
1、简单div布局
css
<div className="table" ref={tableRef}>
<div
className="table-scroll"
style={{ height: height + 'px' }}
ref={scroll}
onScroll={onScroll}
>
<div
className="table-contont"
style={{
height: dataSource.length * itemHeight,
}}
>
<div className="table-list" ref={list}>
{renderList.map((item) => {
return <div className="table-item" key={item}>{item + ''}测试 </div>
})}
</div>
</div>
</div>
</div>
// css 部分
.table {
width: 100%;
height: 600px;
}
.table-scroll {
height: 100%;
overflow-y: scroll;
}
.table-item {
height: 60px;
background-color: salmon;
margin: 5px;
color: white;
border-radius: 40px;
padding-left: 23px;
text-align: left;
line-height: 60px;
}
2、初始化数据
scss
const [dataSource, setDataSource] = useState([]); // 默认的所有数据
const [position, setPosition] = useState([0, 0]); // 定义可视范围内的数据
const tableRef = useRef(null);
const scroll = useRef(null);
const list = useRef(null);
const scrollInfo = useRef({
height: 600,
itemHeight: 60,
renderCount: 0,
bufferCount: 8,
}) // 默认参数项
useEffect(() => {
const { height, itemHeight, bufferCount } = scrollInfo.current
const dataSource = new Array(10000).fill(1).map((item, index) => index + 1)
const renderCount = Math.ceil(height / itemHeight) + bufferCount
scrollInfo.current = { renderCount, height, bufferCount, itemHeight }
setDataSource(dataSource)
setPosition([0, renderCount])
}, [])
const { itemHeight, height } = scrollInfo.current
const [start, end] = position
const renderList = dataSource.slice(start, end) /* 渲染区间 */
在初始化时 useEffect 所做的事情主要是
a、先定义1万条数据
b、通过可视范围的高度和每一条数据的高度,再加上 bufferCount 计算出初始化时候,页面需要加载的数据范围
3、模拟用户滚动行为,在用户滚动滑轮或者滑动屏幕时,相应的滚动列表。我们这里的滚动列表不是真正的滚动列表,而是根据滚动的位置重新渲染可见的列表元素。也就是需要给dom节点增加 onScroll 事件。
上代码
ini
const onScroll = () => {
const { scrollTop } = scroll.current;
const { renderCount, itemHeight } = scrollInfo.current;
const currentOffset = scrollTop - (scrollTop % itemHeight)
list.current.style.transform = `translate3d(0, ${currentOffset}px, 0)` /* 偏移,造成下滑效果 */
const start = Math.floor(scrollTop / itemHeight)
const end = Math.floor(scrollTop / itemHeight + renderCount + 1)
if (end !== position[1] || start !== position[0]) {
/* 如果render内容发生改变,那么截取 */
setPosition([start, end])
}
}
在onScroll滚动时主要做了这几件事
a、通过scrollTop和itemHeight设置 滚动时的 transform 下滑效果
b、在滚动时 通过scrollTop、itemHeight 和 renderCount,来计算出需要 render内容的数据截取