Closed nconfrey closed 6 months ago
hmm that is interesting, the scroll backward is bit tricky, we are measuring and adjusting scroll position to remove this jumping, and it was working great for us.
Also keep in mind that when jumping to place in the list where we didn't scroll, when scrolling backward we only have estimated size that will break if we don't measure.
this happened to me too in Vue, my list only have 7 items and I already put my exact items height to estimateSize (776px). But in my case, not only the scroll up is stuttery, it loop back to previous item when I try to scroll up and create infinite loop of scrolling. It will look like the video below, in this video I try to scroll up but it loop me back to previous item
https://github.com/TanStack/virtual/assets/136794762/2d06b05d-d347-464f-ba89-a1656cc53b22
hmm that is interesting, the scroll backward is bit tricky, we are measuring and adjusting scroll position to remove this jumping, and it was working great for us.
Also keep in mind that when jumping to place in the list where we didn't scroll, when scrolling backward we only have estimated size that will break if we don't measure.
@piecyk what does the code look like to adjust the scroll position?
@nconfrey it's pretty simple, for elements above scroll offset we adjust scroll position with the difference
https://github.com/TanStack/virtual/blob/main/packages/virtual-core/src/index.ts#L662-L673
Experiencing this too but only in iOS Safari.react-virtuoso
suffers from this issue too.
Is there any solution to this? For example, would it be an option to skip the corrections?
Is there any solution to this?
Can you create an minimal reproducible example? It should work out of box, just testes dynamic example on safari and looks fine.
For example, would it be an option to skip the corrections?
Yes, for example passing your own elementScroll https://github.com/TanStack/virtual/blob/main/packages/virtual-core/src/index.ts#L207-L221 that will not add adjustments
Can you create an minimal reproducible example?
Ok, working on it.
Yes, for example passing your own elementScroll https://github.com/TanStack/virtual/blob/main/packages/virtual-core/src/index.ts#L207-L221 that will not add adjustments
Unfortunately, it still doesn't solve the problem. Will try to reproduce it in a sandbox.
I've got it kind of working with the following code. There are still rendering artefacts on mobile as elements are resized, but for our use case it's better than having no virtualisation. It needs a reasonably large overscan (i've got 20) to prevent empty space appearing at the top of the list on certain viewport widths. It's also necessary to kill the cache entirely. The better you can make your estimateSize
method, the fewer rendering artefacts you'll get.
export function VirtualInfiniteScroller<T>(props: VirtualInfiniteScrollerProps<T>) {
const {
rowData,
renderRow,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
estimateRowHeight,
overscan,
} = props;
const listRef = useRef<HTMLDivElement | null>(null);
const estimateHeightWithLoading = (index: number) => {
if (index > rowData.length - 1) {
return LOADING_ROW_HEIGHT;
}
return estimateRowHeight(index);
};
const virtualizer = useWindowVirtualizer({
count: hasNextPage ? rowData.length + 1 : rowData.length,
estimateSize: estimateHeightWithLoading,
overscan: overscan ?? 20,
scrollMargin: listRef.current?.offsetTop ?? 0,
});
const virtualItems = virtualizer.getVirtualItems();
// Kill the cache entirely to prevent weird scrolling issues. This is a hack
virtualizer.measurementsCache = [];
useEffect(() => {
const [lastItem] = [...virtualItems].reverse();
if (!lastItem) {
return;
}
if (
hasNextPage &&
!isFetchingNextPage &&
lastItem.index >= rowData.length - 1
) {
fetchNextPage();
}
}, [
hasNextPage,
fetchNextPage,
rowData.length,
isFetchingNextPage,
virtualItems,
]);
return (
<div ref={listRef} className="List">
<div
style={{
height: virtualizer.getTotalSize(),
width: '100%',
position: 'relative',
}}
>
<div
style={{
position: 'absolute',
top: 0,
left: 0,
width: '100%',
transform: `translateY(${
(virtualItems[0]?.start || 0) - virtualizer.options.scrollMargin
}px)`,
}}
>
{virtualItems.map((virtualItem) => {
return (
<div
key={virtualItem.key}
data-index={virtualItem.index}
ref={(el) => virtualizer.measureElement(el)}
>
{virtualItem.index > rowData.length - 1 ? (
<FlexSpinner />
) : (
renderRow(virtualItem.index)
)}
</div>
);
})}
</div>
</div>
</div>
);
}
I am encountering the same issue with react-virtuoso as you are. Is there any update on this problem?
I am encountering the same issue with react-virtuoso as you are. Is there any update on this problem?
hmm as mention above the size changes when scrolling up that causes the jumping because of scrolling element size change. We can't really fix this on library size as it's tightly coupled with specific implementation
Please use suggested workaround with custom measureElement
Maybe caching total size when scrolling backward also should limit the jumping, overall please share examples then i can have a look.
Has anyone managed to solve the issue of flickering when scrolling in reverse with react-virtuoso? I've tried using increaseViewportBy, overscan, and either itemSize or measureElement, but I'm still experiencing flickering/jumping while scrolling up.
Just ran into this issue with react-virtuoso. Are there any tips on how to fix that?
Just ran into this issue with react-virtuoso. Are there any tips on how to fix that?
It boils down to implementation of the item, why the sizes are changing are you loading some dynamic content. If you an share some stackblitz example maybe we can find a solution.
Ok, got the reproducible example https://stackblitz.com/edit/tanstack-query-dxabsn?file=src%2Fpages%2Findex.js The issue here is that the height of Item is async, in the example above simulating it via setTimeout.
NOTE: we are using vue version of this library.
Adding setTimeout() to :ref="measureElement" function solved the issue.
Note that virtualizer measureElement hack (described in The following code inside of the useVirtualizer fixes the issue:
) is NOT necessary.
const measureElement: VNodeRef = el => {
if (!(el && el instanceof Element)) return
setTimeout(() => { // this!
rowVirtualizer.value.measureElement(el)
})
}
The issue here is that the height of Item is async, in the example above simulating it via setTimeout.
I saw this and tried the workaround.
I added below option to useVirtualizer() for debug. Then I found ResizeObserverEntry comes even without any actual resize.
measureElement: (element, entry, instance) => {
const result = me(element, entry, instance)
console.log(entry ? 'has-entry' : 'not-has-entry', element.getAttribute('data-index'), result)
return result
},
Above log still reproduces even when we removed all of content so its height is zero. (Our row has dynamic content: filled combo box, automatic height textarea and some of filled input)
This does not change with setTimeout() hack, but maybe related?
Then I found ResizeObserverEntry comes even without any actual resize.
yep as the ResizeObserverEntry will also call for initial value
Has anyone solve this problem having dynamic item content? We fetch images from api and when user scrolls really fast down and up - list just jumps. I tried to add some image placeholder and keep the same height for image, but it doesn't help
@piecyk, might the big difference in the elements' size cause scrolling issues? I have 2 lists. On the first one elements' height varies from 250-290px and the scrolling is very smooth, without any issues. On the second list, the first element is over 1000px and the rest is around 250-350px. I set the estimate to 1100px and I can observe the scrolling issues, especially when scrolling through the first big element.
@kamil-homernik could be, if you can create something on stackblitz will have a look.
It was solved by adjusting the JSX structure and elements' sizes. In the end, we have 4 useWindowVirtualizer
instances with dynamic elements' height and it works perfectly. Thanks for this library.
faced with same problem, first time scroll perfectly, when back, scroll flickering ( in debug thousand messages about recalculate ranges getindexes etc and if no try to scroll , messages increase )
It was solved by adjusting the JSX structure and elements' sizes. In the end, we have 4
useWindowVirtualizer
instances with dynamic elements' height and it works perfectly. Thanks for this library.
can you share an example
@andrconstruction in our case there were two problems:
Try to optimise your JSX elements - in our case that was the problem, not virtualizer at all.
hope this helps someone in the future. Used object-fit: contain; height: [some value here] as CSS for img element, and this seems to have solved the issue for me. The suggested solution was giving me issues with scroll restoration. This css hack is not perfect, though, and has some style limitations (especially when images have varying aspect ratios)
I've got it kind of working with the following code. There are still rendering artefacts on mobile as elements are resized, but for our use case it's better than having no virtualisation. It needs a reasonably large overscan (i've got 20) to prevent empty space appearing at the top of the list on certain viewport widths. It's also necessary to kill the cache entirely. The better you can make your
estimateSize
method, the fewer rendering artefacts you'll get.export function VirtualInfiniteScroller
(props: VirtualInfiniteScrollerProps ) { const { rowData, renderRow, hasNextPage, fetchNextPage, isFetchingNextPage, estimateRowHeight, overscan, } = props; const listRef = useRef<HTMLDivElement | null>(null);
const estimateHeightWithLoading = (index: number) => { if (index > rowData.length - 1) { return LOADING_ROW_HEIGHT; } return estimateRowHeight(index); };
const virtualizer = useWindowVirtualizer({ count: hasNextPage ? rowData.length + 1 : rowData.length, estimateSize: estimateHeightWithLoading, overscan: overscan ?? 20, scrollMargin: listRef.current?.offsetTop ?? 0, });
const virtualItems = virtualizer.getVirtualItems();
// Kill the cache entirely to prevent weird scrolling issues. This is a hack virtualizer.measurementsCache = [];
useEffect(() => { const [lastItem] = [...virtualItems].reverse();
if (!lastItem) { return; } if ( hasNextPage && !isFetchingNextPage && lastItem.index >= rowData.length - 1 ) { fetchNextPage(); }
}, [ hasNextPage, fetchNextPage, rowData.length, isFetchingNextPage, virtualItems, ]);
return (
{virtualItems.map((virtualItem) => { return (virtualizer.measureElement(el)} > {virtualItem.index > rowData.length - 1 ? () : ( renderRow(virtualItem.index) )} ); })} </div> </div> </div>
); }
If i am doing this in production I got an error that says unable to read index from undefined.
for
virtualRow.index
, and also dynamic height is not workingpiecyk commented 3 weeks agoIf i am doing this in production I got an error that says unable to read index from undefined. for virtualRow.index, and also dynamic height is not working
@ashishsurya can't really say anything without an example.
ashishsurya commented 3 weeks agoIf i am doing this in production I got an error that says unable to read index from undefined. for virtualRow.index, and also dynamic height is not working
@ashishsurya can't really say anything without an example.
What I meant is that if clear the
measurementCache
, the table is unable to render rows with dynamic height.And also memorization solved my issues, no worries.
Thanks for the reply @piecyk
wwzzyying commented 3 weeks agoDescribe the bug 描述错误
I have a feed of dynamic items, like iframes, photos, and text. When I scroll downward, everything works great and the scrolling is smooth, as measured items that increase in height push the other items down out of sight. However, when I scroll upwards, the performance is super stuttery and the items jump all over the place:我有动态项目的提要,例如 iframe、照片和文本。当我向下滚动时,一切都很好,滚动也很流畅,因为高度增加的测量项目会将其他项目向下推到视线之外。然而,当我向上滚动时,性能非常卡顿,项目跳得到处都是:
Untitled.mov 无题.mov
Your minimal, reproducible example
您的最小的、可重现的示例 Code below: 代码如下:
Steps to reproduce 重现步骤
Create the virtualizer as normal:正常创建虚拟器:
const rowVirtualizer = useVirtualizer({ count: itemCount, getScrollElement: () => parentRef.current, estimateSize: () => 100, overscan: 2 })
Then the feed: 然后是饲料:
const mainFeed = () => { return ( <div style={{ height: `${rowVirtualizer.getTotalSize()}px`, width: '100%', position: 'relative', }} > {rowVirtualizer.getVirtualItems().map((virtualRow) => { const post = loadedPosts[virtualRow.index] return ( <div key={virtualRow.key} data-index={virtualRow.index} ref={rowVirtualizer.measureElement} style={{ position: 'absolute', top: 0, left: 0, width: '100%', transform: `translateY(${virtualRow.start - rowVirtualizer.options.scrollMargin}px)`, display: 'flex', }} > <div style={{ width: '100%' }}> <FeedItem post={post} /> </div> </div> ) })} </div> ) }
Expected behavior 预期行为
Scrolling upwards should be as smooth as scrolling downwards.向上滚动应该与向下滚动一样平滑。
How often does this bug happen?
此错误多久发生一次? Every time 每次
Screenshots or Videos 截图或视频
No response 没有回应
Platform 平台
macOS, Chrome macOS、Chrome
tanstack-virtual version tanstack-虚拟版本
"@tanstack/react-virtual": "^3.0.1"“@tanstack/react-virtual”:“^3.0.1”
TypeScript version TypeScript 版本
No response 没有回应
Additional context 额外的背景信息
The following code inside of the
useVirtualizer
fixes the issue:useVirtualizer
中的以下代码修复了该问题:measureElement: (element, entry, instance) => { const direction = instance.scrollDirection if (direction === "forward" || direction === null) { return element.scrollHeight } else { // don't remeasure if we are scrolling up const indexKey = Number(element.getAttribute("data-index")) let cacheMeasurement = instance.itemSizeCache.get(indexKey) return cacheMeasurement } }
I propose that the above behavior is default, that items are not remeasured when scrolling upward.我建议将上述行为设为默认行为,向上滚动时不会重新测量项目。
Terms & Code of Conduct
条款和行为准则
- [x] I agree to follow this project's Code of Conduct我同意遵守该项目的行为准则[x] I understand that if my bug cannot be reliable reproduced in a debuggable environment, it will probably not be fixed and this issue may even be closed.我知道,如果我的错误无法在可调试环境中可靠地重现,它可能不会被修复,这个问题甚至可能会被关闭。
Using
scrollDirection
to determine theitemSizeCache
can solve most scenarios, but testing shows that some Android devices may experience a sudden forward movement during backward scrolling.
Describe the bug
I have a feed of dynamic items, like iframes, photos, and text. When I scroll downward, everything works great and the scrolling is smooth, as measured items that increase in height push the other items down out of sight. However, when I scroll upwards, the performance is super stuttery and the items jump all over the place:
https://github.com/TanStack/virtual/assets/7350670/655b0a0b-4562-47e2-aa96-cb72daf8ad37
Your minimal, reproducible example
Code below:
Steps to reproduce
Create the virtualizer as normal:
Then the feed:
Expected behavior
Scrolling upwards should be as smooth as scrolling downwards.
How often does this bug happen?
Every time
Screenshots or Videos
No response
Platform
macOS, Chrome
tanstack-virtual version
"@tanstack/react-virtual": "^3.0.1"
TypeScript version
No response
Additional context
The following code inside of the
useVirtualizer
fixes the issue:I propose that the above behavior is default, that items are not remeasured when scrolling upward.
Terms & Code of Conduct