使用 cdk-virtual-scroll 滚动到底部 (Angular 8)

Scroll to bottom with cdk-virtual-scroll (Angular 8)

目标

显示消息列表并在收到新消息时滚动到底部,即使我在顶部也是如此。即使有不同高度的元素,我也想完全滚动到底部。

问题

使用虚拟滚动,我必须设置 [itemSize] 属性,但对我来说它不是一个静态值:

此外,我正在使用 ng-content 从父级插入一个按钮来加载以前的消息。我看到的是,当 _scrollToBottom 被调用时,它没有把我带到底部,而是把我带到了更高一点。我怀疑这是因为虚拟滚动中元素的高度不同。

我已经从 Angular 阅读了这个自动调整滚动策略问题:https://github.com/angular/components/issues/10113; 但我不确定这是否能解决我的问题。

任何关于我能做什么的想法都将受到欢迎。

测试

Codesandbox: https://codesandbox.io/s/angular-virtual-scroll-biwn6

有错误的视频:https://gofile.io/d/8NG9HD


解决方案

Gourav Garg 给出的解决方案有效。只需执行两次滚动功能即可。

我现在正在这样做:


  private _scrollToBottom() {
    setTimeout(() => {
      this.virtualScrollViewport.scrollTo({
        bottom: 0,
        behavior: 'auto',
      });
    }, 0);
    setTimeout(() => {
      this.virtualScrollViewport.scrollTo({
        bottom: 0,
        behavior: 'auto',
      });
    }, 50);
  }

我认为它不是很优雅,但工作正常。

如果你用的是cdk > 7就可以用

this.virtualScrollViewport.scrollToIndex(messages.length-1);

因为这将移至最后一项的顶部。您需要为该项目调用 scrollIntoView。

this.virtualScrollViewport.scrollToIndex(this.numbers.length - 1);
setTimeout(() => {
  const items = document.getElementsByClassName("list-item");
  items[items.length - 1].scrollIntoView();
}, 10);
<cdk-virtual-scroll-viewport #virtualScroll style="height: 500px" itemSize="90">
  <ng-container *cdkVirtualFor="let n of numbers">
    <li class="list-item"> {{n}} </li>
  </ng-container>
</cdk-virtual-scroll-viewport>

我已经更新了你的sandbox

使用可以使用scrollIntoView()滚动到virtualScrollViewport

的底部
this.virtualScrollViewport.scrollIntoView(false);

参考https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

有 cdk scroll 的替代方法。

这是一个通用版本的代码,用于滚动到给定的 HTML 元素。它可以用作 angular 中的服务功能,也可以用作 javascript 中的功能。

scroll(el: HTMLElement, behaviour: any = "smooth", block: any = "start", inline: any = "nearest") {
    el.scrollIntoView({ behavior: behaviour, block: block, inline: inline })
  }

样本HTML:

<div class="scroll-to-top" [ngClass]="{'show-scrollTop': windowScrolled}">
    Back to top<button mat-button mat-icon-button (click)="scrollToTop()">
        <mat-icon>keyboard_arrow_up</mat-icon>
    </button>
</div>

示例 Typescript 组件代码:

@HostListener("window:scroll")
  onWindowScroll() {
    if (window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop > 100) {
      this.windowScrolled = true;
    }
    else if (this.windowScrolled && window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop < 10) {
      this.windowScrolled = false;
    }
  }
  scrollToTop() {
    (function smoothscroll() {
      var currentScroll = document.documentElement.scrollTop || document.body.scrollTop;
      if (currentScroll > 0) {
        window.requestAnimationFrame(smoothscroll);
        window.scrollTo(0, currentScroll - (currentScroll / 8));
      }
    })();
  }

我的特定用例与原来的 post 不同,但相似度足以说明一些问题。如果您尝试使用 scrollToIndex 滚动到 CDK 的虚拟滚动视口的底部,它不起作用,而您最终来到这里,它可能会帮助您了解一些事情。

  1. 视口在 ngOnInit 中完成了异步初始化。 (参见 this code.) So if you try to interact with it before it is finished initializing, it silently fails. (This feature request 至少会在滚动空视口时提供错误。)
  2. 没有任何声明初始化完成。 (参见 this feature request。)

如果您在 cdk-virtual-scroll-viewport 的父组件的 ngAfterViewInit 中直接调用 scrollToIndex,那将不起作用。在使用 scrollToIndex 之前,您必须等待一个滴答声。参见 this Stackblitz。重要位:

ngAfterViewInit(): void {
    // does nothing
    // this.viewPort.scrollTo({ bottom: 0, behavior: 'smooth' });
    this.scrollToIndex$.next();
  }

ngOnInit(): void {
    this.subscriptions.add(
      this.scrollToIndex$
        .pipe(***-->delay(0)<--***)
        .subscribe(() =>
          this.viewPort.scrollTo({ bottom: 0, behavior: 'smooth' })
        )
    );
  }

delay(0) 等待 Angular 生命周期的一个刻度,这似乎足以让视口自行完成。