You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Scrolling up with dynamic heights stutters and jumps #659
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:
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:
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
I agree to follow this project's Code of Conduct
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.
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
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?
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.
exportfunctionVirtualInfiniteScroller<T>(props: VirtualInfiniteScrollerProps<T>){const{
rowData,
renderRow,
hasNextPage,
fetchNextPage,
isFetchingNextPage,
estimateRowHeight,
overscan,}=props;constlistRef=useRef<HTMLDivElement|null>(null);constestimateHeightWithLoading=(index: number)=>{if(index>rowData.length-1){returnLOADING_ROW_HEIGHT;}returnestimateRowHeight(index);};constvirtualizer=useWindowVirtualizer({count: hasNextPage ? rowData.length+1 : rowData.length,estimateSize: estimateHeightWithLoading,overscan: overscan??20,scrollMargin: listRef.current?.offsetTop??0,});constvirtualItems=virtualizer.getVirtualItems();// Kill the cache entirely to prevent weird scrolling issues. This is a hackvirtualizer.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(<divref={listRef}className="List"><divstyle={{height: virtualizer.getTotalSize(),width: '100%',position: 'relative',}}><divstyle={{position: 'absolute',top: 0,left: 0,width: '100%',transform: `translateY(${(virtualItems[0]?.start||0)-virtualizer.options.scrollMargin}px)`,}}>{virtualItems.map((virtualItem)=>{return(<divkey={virtualItem.key}data-index={virtualItem.index}ref={(el)=>virtualizer.measureElement(el)}>{virtualItem.index>rowData.length-1 ? (<FlexSpinner/>) : (renderRow(virtualItem.index))}</div>);})}</div></div></div>);}
Reacted by David Vaness, Lionel Bersée and maysem-ccfdev
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
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.
I experienced this when trying to render a feed of variable height images, and trying to use virtualizer.measureElement. It would work scrolling down, but on scrolling up it would jump and stutter and become unusable, specifically on mobile webkit. The solution for me was to give each <img> an explicit CSS height, fortunately I know the dimensions of each image.
In my case, the scroll issue showed up after navigating to another page and then going back to the previous scroll position.
Scrolling upwards felt really choppy and had no inertia. initialMeasurementsCache only covers part of the list, so it didn’t solve the issue for me.
The solution is to implement a custom measureElement function that avoids remeasuring elements when scrolling upward, using cached measurements instead
Needs
Implement a custom measureElement function that checks scroll direction
Use cached measurements when scrolling up to prevent stuttering
Only remeasure when scroll down or when direction is null
Here's my solution:
constvirtualizer=useWindowVirtualizer({count: allPosts.length,estimateSize: getPostSize,overscan: 10,scrollMargin: parentOffsetRef.current,getItemKey: (index)=>allPosts[index]?.id||`post-${index}`,measureElement: (element,_entry,instance)=>{constdirection=instance.scrollDirection;if(direction==='forward'||direction===null){// Allow remeasuring when scrolling down or direction is nullreturnelement.getBoundingClientRect().height;}else{// When scrolling up, use cached measurement to prevent stutteringconstindexKey=Number(element.getAttribute('data-index'));constcachedMeasurement=instance.measurementsCache[indexKey]?.size;returncachedMeasurement||element.getBoundingClientRect().height;}},});
Reacted by Surya Ashish, Leonidas, Young Oh, WickeyZ, reder_martin, Rumaan K, SecYJ, savosya, Enes Bala, Nickson Yap and 12 more
Reacted by alexswan93
Reacted by Maël, reder_martin, Enes Bala, Sunghwan Bang, BryeWalker, Nickson Yap, beocaca, azinho, jㄐ, Nathan Vasse and 1 more
Reacted by Abdul Wahhab, reder_martin, Enes Bala, Nickson Yap, beocaca, azinho and Nathan Vasse
The solution is to implement a custom measureElement function that avoids remeasuring elements when scrolling upward, using cached measurements instead
Needs
Implement a custom measureElement function that checks scroll direction
Use cached measurements when scrolling up to prevent stuttering
Only remeasure when scroll down or when direction is null
Here's my solution:
const virtualizer = useWindowVirtualizer({
count: allPosts.length,
estimateSize: getPostSize,
overscan: 10,
scrollMargin: parentOffsetRef.current,
getItemKey: (index) => allPosts[index]?.id || post-${index},
measureElement: (element, _entry, instance) => {
const direction = instance.scrollDirection;
if (direction === 'forward' || direction === null) {
// Allow remeasuring when scrolling down or direction is null
return element.getBoundingClientRect().height;
} else {
// When scrolling up, use cached measurement to prevent stuttering
const indexKey = Number(element.getAttribute('data-index'));
const cachedMeasurement = instance.measurementsCache[indexKey]?.size;
return cachedMeasurement || element.getBoundingClientRect().height;
}
},
});
Activity
piecyk commentedon Feb 2, 2024
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.
titodarmawan commentedon Feb 2, 2024
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
Screen.Recording.2024-02-02.at.22.05.53.mov
nconfrey commentedon Feb 6, 2024
@piecyk what does the code look like to adjust the scroll position?
piecyk commentedon Feb 6, 2024
@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
ReidCampbell commentedon Feb 14, 2024
Experiencing this too but only in iOS Safari.
react-virtuososuffers from this issue too.ISilviu commentedon Mar 4, 2024
Is there any solution to this?
For example, would it be an option to skip the corrections?
piecyk commentedon Mar 4, 2024
Can you create an minimal reproducible example? It should work out of box, just testes dynamic example on safari and looks fine.
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
ISilviu commentedon Mar 4, 2024
Ok, working on it.
ISilviu commentedon Mar 4, 2024
Unfortunately, it still doesn't solve the problem. Will try to reproduce it in a sandbox.
carrithers commentedon Mar 21, 2024
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
estimateSizemethod, the fewer rendering artefacts you'll get.nuanyang233 commentedon Apr 19, 2024
I am encountering the same issue with react-virtuoso as you are. Is there any update on this problem?
piecyk commentedon Apr 19, 2024
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
measureElementpiecyk commentedon Apr 20, 2024
Maybe caching total size when scrolling backward also should limit the jumping, overall please share examples then i can have a look.
bukke101 commentedon May 7, 2024
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.
16 remaining items
michaelboyles commentedon Jan 25, 2025
I was able to reduce jumpiness by reducing overscan.
I had it set to 50 for items of height 24px = 1200px of overscan (and I believe that's in each direction...)
In retrospect it was too much. I reduced to 10 and the jumpiness is almost gone. Not perfect, but much better.
scwood commentedon Apr 18, 2025
I experienced this when trying to render a feed of variable height images, and trying to use
virtualizer.measureElement. It would work scrolling down, but on scrolling up it would jump and stutter and become unusable, specifically on mobile webkit. The solution for me was to give each<img>an explicit CSS height, fortunately I know the dimensions of each image.olafsulich commentedon Apr 22, 2025
Removing the flex properties from the parent (with overflow: auto) did the trick for me
dev-folks commentedon May 13, 2025
In my case, the scroll issue showed up after navigating to another page and then going back to the previous scroll position.
Scrolling upwards felt really choppy and had no inertia.
initialMeasurementsCacheonly covers part of the list, so it didn’t solve the issue for me.Here’s a small trick that helped:
hienhaobn commentedon May 28, 2025
The solution is to implement a custom
measureElementfunction that avoids remeasuring elements when scrolling upward, using cached measurements insteadNeeds
measureElementfunction that checks scroll directionHere's my solution:
suryaashish-tyke commentedon Jun 2, 2025
thanks @hienhaobn, this worked for me.
alexswan93 commentedon Apr 3, 2026
@heinhaobn by your efforts you have almost single-handedly brought world peace. I can now scroll in safety.
Although maybe not strictly necessary, I also checked whether it is an Apple device before overriding this logic.
zlanich commentedon Apr 8, 2026
This also worked perfectly for me.
LeandroDoudement commentedon Apr 20, 2026
@nconfrey you're an angel! thank you for the solution!
Virtualize feed list with @tanstack/react-virtual
perf: virtual-core rewrite for mount/measure-storm, plus iOS Safari h…
perf(web): thorough smooth scrolling — large buffer + backward measur…
feat(web): professionalize web UI — Tailwind+Base UI, liquid-glass sk…
feat(web): professionalize web UI — Tailwind+Base UI, liquid-glass sk…
fix: stop stale row heights overlapping cards after tab switch