diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e69644..7dee6a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ # CHANGELOG +## v1.0.4 (2026-05-20) + +### 修复 +- **FastList**: 修复了在绝对底部滚动时,由于 Flex 布局的 `rowGap` 被重复计算到虚拟占位高度中,导致实际内容出现偏移(底部出现无法消除的空白区域)的问题。 +- **FastList**: 修复了极端情况下,滚动高度超过预估总高度时导致列表无法渲染(空白)的边界 Bug。 +- **性能优化**: 在不破坏原有精确滚动计算逻辑的前提下,优化了 `calc` 中的高度累加性能,并修正了测试用例因未触发 `scroll` 事件而导致更新失败的问题。 + ## v1.0.3 (2026-05-18) ### 修复 diff --git a/dist/base.js b/dist/base.js index d64dc70..bb3dea3 100644 --- a/dist/base.js +++ b/dist/base.js @@ -384,11 +384,7 @@ const VirtualScroll = () => { if (listInited) return; const size = list.length, defaultHeight = avg.get() || 32; for (let i = 0; i < size; i++) if (!itemHeights.has(i)) itemHeights.set(i, defaultHeight); - for (let i = 0; i < size; i += groupItemCount) { - let sum = 0; - for (let j = i; j < Math.min(i + groupItemCount, size); j++) sum += itemHeights.get(j); - groupHeights.set(i, sum); - } + for (let i = 0; i < size; i += groupItemCount) groupHeights.set(i, Math.min(groupItemCount, size - i) * defaultHeight); listInited = true; refreshCallback(); }, @@ -398,7 +394,7 @@ const VirtualScroll = () => { itemMarginTop = parseFloat(style.marginTop) || 0; itemMarginBottom = parseFloat(style.marginBottom) || 0; } - if (absoluteIndex === 0) topMargin = itemMarginTop; + if (absoluteIndex === 0 && !topMargin) topMargin = itemMarginTop; const newHeight = node.offsetHeight + itemMarginTop + itemMarginBottom + rowGap; const oldHeight = itemHeights.get(absoluteIndex); if (newHeight !== oldHeight) { @@ -410,23 +406,28 @@ const VirtualScroll = () => { }, calc: (container, list) => { if (!listInited || !list) return null; - const size = list.length, visibleCount = Math.ceil((container.clientHeight || 100) / (avg.get() || 32)); + const size = list.length; + let visibleCount = Math.max(10, Math.ceil((container.clientHeight || 100) / (avg.get() || 32))); let prev = padTop + topMargin + rowGap, post = 0, status = 0, listStartIndex = 0, listEndIndex = 0; + let renderedList = []; + const scrollTop = container.scrollTop; for (let i = 0; i < size; i++) { if (status === 0) { const gh = groupHeights.get(i); - if (gh && prev + gh < container.scrollTop) { + if (gh && prev + gh <= scrollTop && i + groupItemCount < size) { prev += gh; - i += Math.min(groupItemCount, size - i) - 1; + i += groupItemCount - 1; } else { const ih = itemHeights.get(i); - if (prev + ih < container.scrollTop) prev += ih; - else { + if (prev + ih <= scrollTop && i < size - 1) { + prev += ih; + } else { status = 1; let visibleStartIndex = Math.max(0, i); listStartIndex = Math.max(0, visibleStartIndex - visibleCount); listEndIndex = Math.min(listStartIndex + visibleCount * 3, size); i = listEndIndex - 1; + renderedList = list.slice(listStartIndex, listEndIndex); for (let j = listStartIndex; j < visibleStartIndex; j++) prev -= itemHeights.get(j); } } @@ -435,10 +436,14 @@ const VirtualScroll = () => { if (gh) { post += gh; i += groupItemCount - 1; - } else post += itemHeights.get(i); + } else { + post += itemHeights.get(i); + } } } - return { prevHeight: Math.max(0, prev - padTop - topMargin - rowGap), postHeight: post, renderedList: list.slice(listStartIndex, listEndIndex), listStartIndex }; + const finalPrevHeight = Math.max(0, prev - padTop - topMargin - rowGap - (listStartIndex > 0 ? rowGap : 0)); + const finalPostHeight = post > 0 ? Math.max(0, post - 2 * rowGap) : 0; + return { prevHeight: finalPrevHeight, postHeight: finalPostHeight, renderedList, listStartIndex }; } }; }; @@ -510,7 +515,7 @@ Component.register("List", (container) => { if (container.fast) { container.state._listStartIndex = 0; container.state._renderedList = vs.reset(flatList, container) || []; - requestAnimationFrame(() => { + setTimeout(() => { if (container.state._flatList === flatList) vs.init(flatList, container.refresh); }); } else container.state._renderedList = flatList; @@ -533,7 +538,7 @@ Component.register("List", (container) => { }, Util.makeDom( /*html*/ ` -
+