-
Notifications
You must be signed in to change notification settings - Fork 598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
不定高度的虚拟列表 #414
Labels
Comments
我已经发现了问题,就是需要在每次开始时,就需要跟新listItem的数据,下面是更新后的代码<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>不定高度的虚拟列表</title>
</head>
<body>
<style>
.list {
height: 400px;
width: 300px;
outline: 1px solid seagreen;
overflow-x: hidden;
}
.list-item {
outline: 1px solid red;
outline-offset:-2px;
background-color: #fff;
}
</style>
<div class="list">
<div class="list-inner"></div>
</div>
<script>
function throttle(callback) {
let requestId;
return (...args) => {
if (requestId) {return}
requestId = requestAnimationFrame(() => {
callback.apply(this, args);
requestId = null;
});
};
}
const randomIncludes = (min, max) => {
return Math.floor(Math.random()*(max - min + 1) + min);
}
const clientHeight = 400;
const listEl = document.querySelector('.list');
const listInner = document.querySelector('.list-inner');
function initAutoSizeVirtualList(props) {
const cache = [];
// window.cache = cache;
const { listEl, listInner, minSize = 30, clientHeight, items } = props;
// 默认情况下可见数量
const viewCount = Math.ceil(clientHeight / minSize);
// 缓存区数量
const bufferSize = 5;
listEl.style.cssText += `height:${clientHeight}px;overflow-x: hidden`;
// const findItemIndex = (startIndex, scrollTop) => {
// scrollTop === undefined && (
// scrollTop = startIndex,
// startIndex = 0
// )
// let totalSize = 0;
// for(let i = startIndex; i < cache.length; i++) {
// totalSize += cache[i].height;
// if(totalSize >= scrollTop || i == cache.length - 1) {
// return i;
// }
// }
// return startIndex;
// }
// 二分查询优化
const findItemIndex = (startIndex, scrollTop) => {
scrollTop === undefined && (
scrollTop = startIndex,
startIndex = 0
);
let low = startIndex;
let high = cache.length - 1;
const { top: startTop, bottom: startBottom } = cache[startIndex];
while(low <= high) {
const mid = Math.floor((low + high) / 2);
const { top: midTop, bottom: midBottom } = cache[mid];
const top = midTop - startTop;
const bottom = midBottom - startBottom;
if (scrollTop >= top && scrollTop < bottom) {
high = mid;
break;
} else if (scrollTop >= bottom) {
low = mid + 1;
} else if (scrollTop < top) {
high = mid - 1;
}
}
return high;
}
// 更新每个item的位置信息
const upCellMeasure = () => {
const listItems = listInner.querySelectorAll('.list-item');
if(listItems.length === 0){return}
const lastIndex = +listItems[listItems.length - 1].dataset.index;
[...listItems].forEach((listItem) => {
const rectBox = listItem.getBoundingClientRect();
const index = listItem.dataset.index;
const prevItem = cache[index-1];
const top = prevItem ? prevItem.top + prevItem.height : 0;
Object.assign(cache[index], {
height: rectBox.height,
top,
bottom: top + rectBox.height
});
});
// 切记一定要更新未渲染的listItem的top值
for(let i = lastIndex+1; i < cache.length; i++) {
const prevItem = cache[i-1];
const top = prevItem ? prevItem.top + prevItem.height : 0;
Object.assign(cache[i], {
top,
bottom: top + cache[i].height
});
}
}
const getTotalSize = () => {
return cache[cache.length - 1].bottom;
}
const getStartOffset = (startIndex) => {
return cache[startIndex].top;
}
const getEndOffset = (endIndex) => {
return cache[endIndex].bottom;
}
// 缓存位置信息
items.forEach((item, i) => {
cache.push({
index:i,
height: minSize,
top: minSize * i,
bottom: minSize * i + minSize,
isUpdate: false
});
});
return function autoSizeVirtualList(renderItem) {
// 在一开始就需要更新item的位置信息,否则,会出现白屏问题
upCellMeasure();
const startIndex = findItemIndex(listEl.scrollTop);
const endIndex = startIndex + viewCount;
// console.log(startIndex, findItemIndex(startIndex, clientHeight))
const startBufferIndex = Math.max(0, startIndex - bufferSize);
const endBufferIndex = Math.min(items.length-1, endIndex + bufferSize);
const renderItems = [];
for(let i = startBufferIndex; i <= endBufferIndex; i++) {
renderItems.push(renderItem(items[i], cache[i]))
}
// 在此处更新,顶部会有白屏
// upCellMeasure();
const startOffset = getStartOffset(startBufferIndex);
const endOffset = getTotalSize() - getEndOffset(endBufferIndex);
listInner.style.setProperty('padding', `${startOffset}px 0 ${endOffset}px 0`);
return renderItems;
}
}
// 模拟10万条数据
const count = 100000;
const items = Array.from({ length: count }).map((item, i) => ({ name: `item ${(i+1)}`, height: randomIncludes(40, 120) }) );
const autoSizeVirtualList = initAutoSizeVirtualList({ listEl, listInner, clientHeight, items });
document.addEventListener('DOMContentLoaded', () => {
const renderItems = autoSizeVirtualList((item, rectBox) => {
return `<div class="list-item" data-index="${rectBox.index}" style="height:${item.height}px">${item.name}</div>`
});
listInner.innerHTML = renderItems.join('');
});
listEl.addEventListener('scroll', throttle(() => {
const renderItems = autoSizeVirtualList((item, rectBox) => {
return `<div class="list-item" data-index="${rectBox.index}" style="height:${item.height}px">${item.name}</div>`
});
listInner.innerHTML = renderItems.join('');
}));
</script>
</body>
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
我自己写了一个不定高度的虚拟列表demo,我给了一定的缓存区,当滚轮移动过快时,仍然会存在白屏问题,能不能帮我解决一下
The text was updated successfully, but these errors were encountered: