虚拟滚动视口中混合高度项目的自定义scrollToIndex

问题描述 投票:0回答:1
  • 我正在使用 CDK 虚拟滚动来显示页面上的项目列表。最坏情况下的项目数量约为 2K。
  • 我正在使用默认的固定大小滚动策略。
  • 数据源是具有分页功能的DataSource。获取后续页面时存在一些延迟。
  • 列表元素可以根据其包含的数据具有不同的高度。
  • 列表元素之前有一些非虚拟化的内容。我正在使用单独的视口和滚动元素

我想要解决的问题是能够使用其索引以编程方式滚动到某个项目。示例 API 是:

async scrollToIndex(viewPort: CdkVirtualScrollViewport, index: number): Promise<boolean>

以下是我正在使用的方法的高级概述:

  1. 用户开始滚动到索引 N
  2. 使用公式
    offset + (N * itemSize)
    猜测索引 N 的偏移量。由于存在不同的 itemSize,因此该猜测预计不准确。
  3. 滚动到此偏移并使用
    const range = viewport.getRenderedRange()
  4. 获取渲染范围
  5. 有3种可能
    • N < range.start
      - 猜测太高,纠正偏移并转到 2
    • N >= range.end
      - 猜测太低,纠正偏移并转到 2
    • range.start <= N < range.end
      - 猜测足够好,可以将 N 带入视口,转到 5
  6. 最终滚动 - 使用 scrollIntoView() 将索引为 N 的元素滚动到顶部

这种方法有效,但存在一些问题:

  1. 步骤3中的操作之间需要有延迟
      guess = this.getGuess(index, offset, itemHeight);
      viewPort.scrollToOffset(guess);
      await delayFor(400); // ARBITRARY DELAY
      const renderedRange = viewPort.getRenderedRange();
  1. 有时第 5 步中的 scoll 不会将元素带到顶部,它会偏离几个像素

我需要它在不同的移动设备上工作,这些设备具有不同的性能特征,所以我不能使用任意延迟,它可以在我的电脑/设备上工作,但对于其他一些设备,延迟可能不够。 我正在寻找一种没有硬编码延迟的更好解决方案。例如等待一些事件,表明在

scrollToOffset()
调用后范围已更改。

关于问题2,我不确定为什么会发生,因此不知道如何解决它。我对解决第一个问题更感兴趣,但感谢任何帮助。在示例中,滚动到索引 2 给出了错误的结果。

我创建了一个最小的示例here - 在这个示例中,每个第三个列表元素都比其他元素高。

angular scroll angular-cdk angular-cdk-virtual-scroll
1个回答
0
投票

要解决您的问题,请尝试这种方法,不要造成不必要的延迟,并更有效地处理滚动。另外,对于元素未滚动到顶部的问题,请使用scrolledIndexChange事件来跟踪视口何时完成滚动,然后将元素滚动到视图中。

import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';

async function scrollToIndex(viewport: CdkVirtualScrollViewport, index: number): Promise<boolean> {
  // Step 1: Make a guess for the offset
  const itemHeight = getItemHeight(index); // Implement a function to get the item height based on the index

  const guess = index * itemHeight;

  // Step 2: Scroll to the guessed offset
  viewport.scrollToOffset(guess);

  // Step 3: Wait for the viewport to finish scrolling
  await new Promise(resolve => {
    const subscription = viewport.scrolledIndexChange.subscribe(() => {
      subscription.unsubscribe();
      resolve();
    });
  });

  // Step 4: Get the rendered range
  const renderedRange = viewport.getRenderedRange();

  // Step 5: Check if the item is in the viewport
  if (index < renderedRange.start) {
    // The guess was too high, correct offset and scroll again
    return scrollToIndex(viewport, index);
  } else if (index >= renderedRange.end) {
    // The guess was too low, correct offset and scroll again
    const correctedOffset = (index + 1) * itemHeight - viewport.getViewportSize().height;
    viewport.scrollToOffset(correctedOffset);
    return scrollToIndex(viewport, index);
  } else {
    // The guess is good enough, scroll the element into view
    const element = viewport.elementRef.nativeElement.querySelector(`[cdkvirtualforitemindex="${index}"]`);
    if (element) {
      element.scrollIntoView({ behavior: 'auto', block: 'start', inline: 'nearest' });
      return true;
    }
    return false;
  }
}

// Example usage:
const indexToScroll = 2; // Replace with the desired index
await scrollToIndex(yourViewportInstance, indexToScroll);
© www.soinside.com 2019 - 2024. All rights reserved.