简体   繁体   中英

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

Goal

Display a list of messages and scroll to the bottom when a new message is received, even when I am at the top. I would like to scroll fully bottom even with elements of different heights.

Problem

With virtual scroll, I have to set the [itemSize] property, but for me it is not a static value:

  • When a message is too long for one line it breaks in multiple, so its height changes.
  • I have different types of messages with different heights (system messages for example).

Also, I am using ng-content to insert a button from the parent to load previous messages. What I see is that, when _scrollToBottom is invoked, instead of taking me to the bottom, it takes me to a bit higher. I suspect this is because of the different heights of elements inside virtual-scroll.

I have read this autosize scroll strategy issue from Angular: https://github.com/angular/components/issues/10113; but I am not sure this will solve my problem.

Any idea of what I could do will be welcome.

Test

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

  • When messages are loaded, scroll up.
  • Send message. (When the new message is loaded, instead of scrolling to bottom, the virtual-scroll stops a little higher)
  • Repeat

Video with the error: https://gofile.io/d/8NG9HD


Solution

The solution given by Gourav Garg works. Simply by executing twice the scroll function.

I am doing this now:


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

I think it is not very elegant but works fine.

You can use if you are using cdk > 7

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

As this will move to the top of last item. You need to call scrollIntoView for that item.

 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>

I have updated your sandbox

There's an alternative to cdk scroll.

Here's a genric version of code to scroll to a given HTML element. It can be used as a service function in angular or as a function in javascript.

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

Sample 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>

Sample Typescript component code:

@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));
      }
    })();
  }

My particular use case was different from the original post, but similar enough to say something. If you are trying to scroll to the bottom of the CDK's virtual scrolling viewport using scrollToIndex , it isn't working, and you end up here, it might help you to know a few things.

  1. The viewport finishes initializing itself asynchronously in its ngOnInit . (See this code .) So if you try to interact with it before it is finished initializing, it silently fails. (This feature request would at least provide an error when an empty viewport is scrolled.)
  2. It has nothing declaring that initialization is finished. (See this feature request .)

If you call scrollToIndex directly in ngAfterViewInit on cdk-virtual-scroll-viewport 's parent component, that won't work. You have to wait a tick before using scrollToIndex . See this Stackblitz . The important bits:

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) waits one tick of the Angular lifecycle, which seems to be enough to allow the viewport to finish itself up.

Use can use scrollIntoView() to scroll to the bottom of virtualScrollViewport

this.virtualScrollViewport.scrollIntoView(false);

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM