T
TanStack•3y ago
equal-aqua

Scroll Anchoring

Is there a way using react-virtual (v3) to maintain the apparent scroll position when new items are added to the start of the list? I'm seeing in my app that if new items are added to the top and the scrolling is somewhere down the list that what the user sees moves as items appear at the top. The behavior I hope to achieve is that when the user has already scrolled down, as items appear at the top the apparent position of the scrolling stays constant. I believe this called scroll anchoring. I'm not sure if this is due to my implementation or if it's something that needs to be supported by react-virtual. Thanks!
11 Replies
correct-apricot
correct-apricot•3y ago
Hi, checkout the reversed virtual list discussions https://github.com/TanStack/virtual/discussions/195 there you can find this example https://codesandbox.io/p/sandbox/beautiful-meninsky-fr6csu
GitHub
Any guidance on a reversed virtual list with dynamic elements? · Ta...
We have been using react-virtual for a few months, and love the fact that the library is headless and allow us to do things our way... However, we are trying to build a chat-like virtual list that ...
beautiful-meninsky-fr6csu
CodeSandbox is an online editor tailored for web applications.
equal-aqua
equal-aquaOP•3y ago
I'll try this - It looks like this might be the perfect solution (for me with useVirtualizer), thank you. It might be worth putting this up as a recipe on the TanStack Virtual docs @piecyk I notice your example using display: block instead of position: absolute with translate, this is different than any of the example on the react-virtual homepage. It this switched to support the content pane resizing via padding in your example? Is this a better approach than the absolute + translate?
correct-apricot
correct-apricot•3y ago
it's better is some cases, like when you want to change size of item by action or animate size, but most cases there are same.
equal-aqua
equal-aquaOP•3y ago
I occasionally saw rows overlapping, maybe this address that also the overlap went away when scrolling away and returning
equal-aqua
equal-aquaOP•3y ago
@piecyk I learnt from your sandbox code and reproduced the issue (and linked a sandbox below to demonstrate it). I made a list which can have new items added with 'Add item' button, and older pages of items can be loaded by clicking 'Load page' button. This list type is useful because it can display huge list from the backend without loading everything, and can new items added. The issue is that when the list is already scrolled down, adding new items (or loading old items) will cause the list to scroll (depending on the list direction, in my case bottom to top - there's a checkbox to reverse direction). What I'm trying to achieve is that neither of these operations would cause the list to scroll. Here's my sandbox in case you have any suggestion: https://stackblitz.com/edit/react-ts-fsntoa?file=App.tsx
Jim Redfern
StackBlitz
React Ts (forked) - StackBlitz
React + TypeScript starter project
correct-apricot
correct-apricot•3y ago
@Jim Redfern had a quick look and got something like this, hope it helps a bit with your use case, https://stackblitz.com/edit/react-ts-xfsmi9?file=App.tsx also some points * when container size is less than elements, we need to skip the adjustment as there is no place to scroll * we also need to know when prepending items, current i did this naive check if 1 or 50 items * we need to update scrollOffset on instance in same render, but actually scroll after react flush changes something is still off when loading fist page and and we have less elements that container size but should be good start for you
Damian Pieczynski
StackBlitz
React Ts (forked) - StackBlitz
React + TypeScript starter project
equal-aqua
equal-aquaOP•3y ago
Thanks some useful points here I did manage to achieve something similar by processing pending in the layoutEffect. However I actually need to also recognize removal of items aswell which can be anywhere in the list. In the end it seemed this can't be differentiated deriving purely by comparing the before and after virtualizer. So I'm now passing in the added or removed list into the renderer and this easily lets me know where the change has taken place (above or below the first virtual item in the list), and this determines if the scroll offset needs adjusting up, down or not at all. I'll post my stackblitz once it's working (🤞) for you to see. It would be a cool feature if react-virtual provided helper to support not scrolling the list when it's scrolled halfway down and items are added to the bottom and top, not sure if that's feasible though. I'm trying to figure out what the scrollOffset change should be when an item is removed from above, I thought it would be
const nextScrollOffset = virtualizer.scrollOffset - removedItems.length * itemEstimateSize;
const nextScrollOffset = virtualizer.scrollOffset - removedItems.length * itemEstimateSize;
but this doesn't seem to be the case. When I press delete sometimes the list moves the size estimate and sometimes it moves more I'll make a sandbox which demonstrates this
correct-apricot
correct-apricot•3y ago
Yep, it's not same case when removing, as in dynamic sizes most cases real sizes are different form estimated, but that we can get from measurementsCache array, it's exposed on virtualiser.
equal-aqua
equal-aquaOP•3y ago
awesome information thanks @piecyk, I'll check this out! I got this working, just trying to compensate for zoom level now, currently at any zoom which is not 100% the offset is slightly off I'm guessing I need to incorporate window.devicePixelRatio
equal-aqua
equal-aquaOP•3y ago
equal-aqua
equal-aquaOP•3y ago
I didn't solve for slight error when zoomed in or out, and there's also an error when adding 25 items to the top when scrolled to the very bottom. I had to inject the changes (addedItems / removedItems), I found it difficult to calculate these just from the data. Maybe it's possible now that I've figured out everything else though Oh I broke it.. lets see Fixed now I learned my lesson - if I have something working in a sandbox, make a backup before refactoring It's really strange, I swear that previously it was working by accessing the virtualizer.measurementsCache with updated values after the item was deleted above. However I couldn't reproduce that so I calculated the height from the old measurementsCache and filtering the deleted items this approach seems good to me but I do wonder if there's a more efficient way with react-virtual maybe something like this useScrollAnchor could be included in the lib but better if it didn't need to be informed about added/removed

Did you find this page helpful?