vaadin / web-components

A set of high-quality standards based web components for enterprise web applications. Part of Vaadin 20+
https://vaadin.com/docs/latest/components
461 stars 83 forks source link

[virtualizer] Scroll offset is not always restored after size change #7237

Open vursen opened 7 months ago

vursen commented 7 months ago

Description

Follow-up to https://github.com/vaadin/web-components/pull/7205

The virtualizer doesn't always restore the first visible item's scroll offset after the size change. This tends to happen when the list size > 100 000 and the size change doesn't affect the currently visible items.

Expected outcome

The scroll offset should be restored after the size change.

Minimal reproducible example

it('should preserve scroll position when size decrease does not affect any rendered indexes', async () => {
  // Scroll to an index and add an additional scroll offset.
  const index = virtualizer.size - 2000;
  virtualizer.scrollToIndex(index);
  scrollTarget.scrollTop += 10;

  // Decrease the size so that no rendered indexes are affected.
  virtualizer.size -= 1000;
  await oneEvent(scrollTarget, 'scroll');

  const item = elementsContainer.querySelector(`#item-${index}`);
  expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1);
});

it('should preserve scroll position on size increase', async () => {
  const index = virtualizer.size - 2000;
  virtualizer.scrollToIndex(index);
  scrollTarget.scrollTop += 10;

  virtualizer.size += 1000;
  await oneEvent(scrollTarget, 'scroll');

  const item = elementsContainer.querySelector(`#item-${index}`);
  expect(item.getBoundingClientRect().top).to.be.closeTo(scrollTarget.getBoundingClientRect().top - 10, 1);
});

Steps to reproduce

--

Environment

Vaadin version(s): 24.4

Browsers

No response

vursen commented 7 months ago

The scroll offset appears to be reset by _adjustVirtualIndexOffset that is called on scroll event following scrollToIndex that restores the scroll position.

vursen commented 7 months ago

I made an attempt to fix the issue by skipping _adjustVirtualIndexOffset in _scrollHandler if the scroll position hasn't changed since the previous scroll event. However, it revealed that the _adjustVirtualIndexOffset call – happening on scroll event that follows scrollToIndex – is important. At least, the scroll height doesn’t get always updated correctly without it. It's curious because scrollToIndex has its own logic updating _vidxOffset and I would expect no need for any further adjustments.

Besides, I noticed that scrolling to an index near the end of a large list can cause _adjustVirtualIndexOffset to be called just as many times as there are items rendered in the DOM. The reason is again scrollToIndex apparently calculating _vidxOffset inaccurately so that another adjustment is needed on the following scroll event.

image