不定高度虚拟滚动实现方案
作者:秋了秋 发表时间:2023年12月09日
Web前端界的虚拟滚动方案都是假设所有行高一样,通过计算屏幕可视高度计算出科容纳有多少行数据从而插入多少行dom,至于滚动条是计算所有行需要的高度然后用padding撑起来。这种虚拟滚动有一个缺陷就是必须是单行,或者每行数据高度一致,不允许任意换行,如何解决这个痛点?以下是我研究的实现方案,可参考。
<div style="height:400px;width:800px;overflow: auto;border: 1px solid #ccc" id="scroller"><div id="container" ></div></div>
<script type="text/javascript">
// 模拟高度不等的一万条数据
let dataArray = [], beginIndex = 0, segIndex = 0, beginSetHeight, lastBeginIndex, lastType;
for(let i = 0; i < 10000; i++) {
dataArray.push(i + '. ' + new Array(Math.ceil(Math.random() * 10)).fill('数据数据数据数据数据数据数据数据'));
}
let minCount = parseInt(parseInt(scroller.style.height) / 16); // 假设最小行高为16像素(一行字体的高度)
// 初始化往容器填充维持滚动的最小量数据
addContent('append');
const scrollHeight = parseInt(scroller.style.height);
let scrollTimer;
scroller.onscroll = (e)=>{
if(scrollTimer || beginSetHeight) {
return; //beginSetHeight 禁止因设置高度触发onscroll
}
scrollTimer = setTimeout(function() {
// 视窗高度
var containerHeight = parseFloat(container.offsetHeight);
// 滚动条位置
var scrollTop = scroller.scrollTop;
if (scrollTop + scrollHeight >= containerHeight) {
console.log('滚动到达底部');
addContent('append');
} else if(scrollTop <= 0) {
console.log('滚动到达顶部');
addContent('prepend');
}
clearTimeout(scrollTimer);
scrollTimer = null;
}, 100)
}
function solveIndex() {
// index临界处理
if(beginIndex <= 0) {
beginIndex = 0;
}
if(beginIndex + minCount >= dataArray.length - 1) {
beginIndex = dataArray.length - 1 - minCount;
}
}
function addContent(type) {
const seg = document.createElement('div');
seg.className = 'seg seg' + segIndex++;
if(lastBeginIndex === beginIndex && lastType === type) {
return;
}
beginSetHeight = true;
// lastType !== type 反向滚动
if(lastType !== type && lastType) {
if(type === 'append') {
beginIndex += minCount * 3;
} else {
beginIndex -= minCount * 3;
}
solveIndex();
}
for (let i = beginIndex; i < beginIndex + minCount; i++) {
const p = document.createElement('p');
p.innerHTML = dataArray[i];
seg.appendChild(p);
}
if(type === 'append') {
container.appendChild(seg);
} else {
container.insertBefore(seg, document.querySelector('.seg'));
scroller.scrollTop = seg.offsetHeight;
}
lastBeginIndex = beginIndex;
lastType = type;
// 清理上上一个seg,页面始终只保留两个有内容的seg
const segs = container.querySelectorAll('.seg');
if(segs.length > 2) {
const removeSeg = type === 'append' ? segs[0] : segs[segs.length - 1];
const originScrollTop = scroller.scrollTop, height = removeSeg.offsetHeight;
container.removeChild(removeSeg);
scroller.scrollTop = type === 'append' ? originScrollTop - height : originScrollTop;
}
if(type === 'append') {
beginIndex += minCount;
} else {
beginIndex -= minCount;
}
solveIndex();
setTimeout(function() {
beginSetHeight = false; //禁止因设置高度和代码设置scrollTop触发onscroll
}, 100)
}
</script>
直接复制代码放到html页面里面的body标签内即可看到效果
0
文章作者: “秋了秋”个人博客,本站鼓励原创。
转载请注明本文地址:http://netblog.cn/blog/506.html