Skip to content
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

Infinite scroll in *both* directions (i.e. down and up) #317

Closed
rossknudsen opened this issue Aug 30, 2019 · 11 comments
Closed

Infinite scroll in *both* directions (i.e. down and up) #317

rossknudsen opened this issue Aug 30, 2019 · 11 comments

Comments

@rossknudsen
Copy link

Hey all,

I had to implement a time picker for work that was modeled off of an existing UWP/WPF component. The picker could scroll infinitely (or at least appeared to) in either direction, allowing the user to scroll to find the time they wanted in either direction.

I forked an existing codesandbox example to prove that react-window could do the job - which it did. But one thing I couldn't achieve was the ability to scroll infinitely in the upward direction. I'm guessing that the list index is bounded to zero and cannot be negative, so I worked around it by setting the starting point far into the "future" thereby setting the index to a high number. See the line:

const numberOfDaysToScrollTo = 1000;

Just wondering if there is another way to solve this, or whether its a feature that you'd consider adding - it may have been proposed before.

But anyway, thanks for the awesome library.

@bvaughn
Copy link
Owner

bvaughn commented Aug 31, 2019

There's no way to scroll past an offset of 0, unfortunately. So the only way you could implement something like that would be to use a hack similar to what you describe.

Bidirectional infinite scrolling doesn't sound like a common enough use case (in my opinion) to be something I'd want to invest a ton of effort into to be honest.

@rossknudsen
Copy link
Author

No worries, just thought I'd post in case there was some value in it. Will close.

Maybe a newsfeed that loads new stories at the top would be another example?

@AliNazariii
Copy link

Hi @bvaughn
So there isn't any solution for bidirectional infinite scrolling with virtualized lists? or any trick you suggest?

@osadchiynikita
Copy link

osadchiynikita commented Nov 5, 2020

@AliNazariii +1

@bvaughn This feature would be very helpful and great extension for this awesome library (which solving big pain already).

For example "vertical chat messages list" - Initially It should be rendered with scrolled to the latest item, and user should scroll backwards through the chat history.

I'm currently trying to find some hack for backwards vertical scrolling for exact case in my app.

@AliNazariii
Copy link

Hi @osadchiynikita, Please inform me if you found a solution to use the virtualized list in a messaging app.

@osadchiynikita
Copy link

@AliNazariii

I’m trying to do POC now using another lib - https://virtuoso.dev/

@AliNazariii
Copy link

@osadchiynikita yeah, I tried it, too. But it had some problems in a bidirectional scroll. On backward vertical scrolling, it goes up and then it comes where I was before the scroll.
But tell me your result after your trying.

@osadchiynikita
Copy link

@AliNazariii Yes, I also have issue with scrollbar position after load more 😕

@uncvrd
Copy link

uncvrd commented Dec 30, 2020

I had the same issue with Virtuoso because it seems to need to update two props to scroll when using backwards vertical scrolling. What did you guys end up using for virtual scroll??

@birdwell
Copy link

birdwell commented Feb 9, 2021

@osadchiynikita @AliNazariii I'm in need of bi-directional infinite scroll component as well. Did either of y'all land on a solution or able to hack together something with an existing library?

@ABruel
Copy link

ABruel commented Mar 1, 2021

@osadchiynikita @AliNazariii @birdwell @rossknudsen

I have something similar implemented, only I use it on the horizontal axis. Here's the excerpt that handles this. I also don't just add new items, but take items from one end add more on the other side.

I'll be happy to answer any questions and also would love to hear any ideas for optimizing this mess.

One more thing, this is just the excerpt that handles the removing/adding items and repositioning the scroll, some variables might be hanging or missing.

// this would most likely be better with onLayoutEffect, to prevent unnecessary paints.
useEffect(() => {
  if (!refPrefScroll.current) return;
  // diffPeriodo - Math.abs(internalPeriodo[1].diff(internalPeriodo[0], 'days')) + 1, basically it's the number of total items rendered by the list
  // widthDia - item width.
  // zoom - number of items being shown on the screen (container.clientWidth / widthDia) +-.
  // left, new scrool left that should keep the previous day visible
  let left = (diffPeriodo / 2) * widthDia - widthDia / 2;
  if (refPrefScroll.current.dir === 'left' && zoom > 1)
    left -= (zoom - 1) * widthDia;
  refPrefScroll.current = undefined;
  document.getElementById(areasProp[0].key + ':trilho')?.scrollTo({
    left,
  });
}, [refPrefScroll.current]);

const handleScroll = React.useCallback(
  (event, areaKey) => {
    // header - the outside element of the grid, the container of all items
    const header = document.getElementById(ID_CABECALHO);
    if (!header) return;
    // periodoHandling  is just a control prop, it determines if the periodo should be changed, allowPeriodoChange is just a lock for another part of the system that needs to prevent the change for the next render.
    if (periodoHandling === 'dynamic' && refControleScrollZoom.current.allowPeriodoChange) {
      if (event.scrollLeft + header.offsetWidth >= header.scrollWidth) {
        setInternalPeriodo((cur) => {
          const newP = cur.slice() as [moment.Moment, moment.Moment];
          newP[0] = moment(newP[0]).add(15, 'day');
          newP[1] = moment(newP[1]).add(15, 'day');
          load(false, newP); // this just loads new items. false make it not force the new download if not necessary, newP is an override for the time frame, you can consider newP the new page.
          return newP;
        });
        // here i can use ref since the only update that matters on this happens at the same time a setState is called
        refPrefScroll.current = {  dir: 'left' };
      } else if (event.scrollLeft === 0) {
        setInternalPeriodo((cur) => {
          const newP = cur.slice() as [moment.Moment, moment.Moment];
          newP[0] = moment(newP[0]).subtract(15, 'day');
          newP[1] = moment(newP[1]).subtract(15, 'day');
          load(false, newP);
          return newP;
        });
        refPrefScroll.current = {  dir: 'right' };
      }
    }
  },
  [],
);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants