web-dev-qa-db-ja.com

angular2一番下までスクロール(チャットスタイル)

Ng-forループ内に一連の単一セルコンポーネントがあります。私はすべてを整えていますが、適切なものを見つけ出すことはできません。

現在私は

setTimeout(() => {
  scrollToBottom();
});

しかし、これは画像が非同期的にビューポートを下に押し下げるので、いつもうまくいくとは限りません。

Angular 2でチャットウィンドウの一番下までスクロールする適切な方法は何ですか?

87
Max Alexander

同じ問題がありました。 AfterViewChecked@ViewChild の組み合わせ(Angular 2 beta 3)を使用しています。

コンポーネント:

import {..., AfterViewChecked, ElementRef, ViewChild, OnInit} from 'angular2/core'
@Component({
    ...
})
export class ChannelComponent implements OnInit, AfterViewChecked {
    @ViewChild('scrollMe') private myScrollContainer: ElementRef;

    ngOnInit() { 
        this.scrollToBottom();
    }

    ngAfterViewChecked() {        
        this.scrollToBottom();        
    } 

    scrollToBottom(): void {
        try {
            this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
        } catch(err) { }                 
    }
}

テンプレート:

<div #scrollMe style="overflow: scroll; height: xyz;">
    <div class="..." 
        *ngFor="..."
        ...>  
    </div>
</div>

もちろんこれはかなり基本的なことです。 AfterViewCheckedは、ビューがチェックされるたびに起動します。

コンポーネントのビューをチェックするたびに通知を受け取るようにこのインターフェースを実装します。

たとえば、メッセージを送信するための入力フィールドがある場合、このイベントはキーアップのたびに発生します(例を示すために)。ただし、ユーザーが手動でスクロールしたかどうかを保存してからscrollToBottom()をスキップしても問題ありません。

156

これに対する最も簡単で最良の解決策は:です

この#scrollMe [scrollTop]="scrollMe.scrollHeight"シンプルなものをテンプレート側に追加

<div style="overflow: scroll; height: xyz;" #scrollMe [scrollTop]="scrollMe.scrollHeight">
    <div class="..." 
        *ngFor="..."
        ...>  
    </div>
</div>

WORKING DEMO (ダミーチャットアプリを使用)AND- フルコード

上記のデモはAngular5で行われたように、Angular2と最大5つで動作します。


注意 :

エラーの場合:ExpressionChangedAfterItHasBeenCheckedError

Angular側ではなくcss側の問題であるcssを確認してください。ユーザー@KHANの1人はdivからoverflow:auto; height: 100%;を削除することでそれを解決しました。 (詳細については会話を確認してください)

125
Vivek Doshi

使用を検討してください

.scrollIntoView()

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView を参照してください。

17
Boris Yakubchik

ユーザーが上にスクロールしようとしたかどうかを確認するためのチェックを追加しました。

誰かがそれを望めば私はここにこれを残すつもりです:)

<div class="jumbotron">
    <div class="messages-box" #scrollMe (scroll)="onScroll()">
        <app-message [message]="message" [userId]="profile.userId" *ngFor="let message of messages.slice().reverse()"></app-message>
    </div>

    <textarea [(ngModel)]="newMessage" (keyup.enter)="submitMessage()"></textarea>
</div>

そしてコード:

import { AfterViewChecked, ElementRef, ViewChild, Component, OnInit } from '@angular/core';
import {AuthService} from "../auth.service";
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';
import 'rxjs/add/operator/concatAll';
import {Observable} from 'rxjs/Rx';

import { Router, ActivatedRoute } from '@angular/router';

@Component({
    selector: 'app-messages',
    templateUrl: './messages.component.html',
    styleUrls: ['./messages.component.scss']
})
export class MessagesComponent implements OnInit {
    @ViewChild('scrollMe') private myScrollContainer: ElementRef;
    messages:Array<MessageModel>
    newMessage = ''
    id = ''
    conversations: Array<ConversationModel>
    profile: ViewMyProfileModel
    disableScrollDown = false

    constructor(private authService:AuthService,
                private route:ActivatedRoute,
                private router:Router,
                private conversationsApi:ConversationsApi) {
    }

    ngOnInit() {

    }

    public submitMessage() {

    }

     ngAfterViewChecked() {
        this.scrollToBottom();
    }

    private onScroll() {
        let element = this.myScrollContainer.nativeElement
        let atBottom = element.scrollHeight - element.scrollTop === element.clientHeight
        if (this.disableScrollDown && atBottom) {
            this.disableScrollDown = false
        } else {
            this.disableScrollDown = true
        }
    }


    private scrollToBottom(): void {
        if (this.disableScrollDown) {
            return
        }
        try {
            this.myScrollContainer.nativeElement.scrollTop = this.myScrollContainer.nativeElement.scrollHeight;
        } catch(err) { }
    }

}
15
robert king

受け入れられた答えはメッセージをスクロールしている間発火します、これはそれを避けます。

あなたはこのようなテンプレートが欲しいです。

<div #content>
  <div #messages *ngFor="let message of messages">
    {{message}}
  </div>
</div>

次に、ViewChildrenアノテーションを使用して、ページに追加されている新しいメッセージ要素を購読します。

@ViewChildren('messages') messages: QueryList<any>;
@ViewChild('content') content: ElementRef;

ngAfterViewInit() {
  this.messages.changes.subscribe(this.scrollToBottom);
}

scrollToBottom = () => {
  try {
    this.content.nativeElement.scrollTop = this.content.nativeElement.scrollHeight;
  } catch (err) {}
}
10
denixtry

* ngForが終了した後に最後までスクロールしていることを確認したい場合は、これを使用できます。

<div #myList>
 <div *ngFor="let item of items; let last = last">
  {{item.title}}
  {{last ? scrollToBottom() : ''}}
 </div>
</div>

scrollToBottom() {
 this.myList.nativeElement.scrollTop = this.myList.nativeElement.scrollHeight;
}

ここで重要なのは、"last"変数が現在最後の項目にいるかどうかを定義するので、"scrollToBottom"メソッドをトリガーできることです。

8
Mert
this.contentList.nativeElement.scrollTo({left: 0 , top: this.contentList.nativeElement.scrollHeight, behavior: 'smooth'});
6
Stas Kozlov

私は自分の解決策を共有しました。私は残りに完全に満足していなかったからです。 AfterViewCheckedname__に関する私の問題は、時々私が上にスクロールしていることです、そして、何らかの理由で、このlife hookが呼ばれて、新しいメッセージがなかったとしてもそれは私を下にスクロールします。私はOnChangesname__を使用しようとしましたが this 問題でした、 this 解決策を導きました。残念ながら、DoCheckname__のみを使用した場合、メッセージがレンダリングされる前にスクロールダウンしていたため、これも役に立ちませんでした。そのため、AfterViewCheckedname__を呼び出す必要がある場合はDoCheckで基本的にscrollToBottomname__を指定します。

フィードバックをお待ちしています。

export class ChatComponent implements DoCheck, AfterViewChecked {

    @Input() public messages: Message[] = [];
    @ViewChild('scrollable') private scrollable: ElementRef;

    private shouldScrollDown: boolean;
    private iterableDiffer;

    constructor(private iterableDiffers: IterableDiffers) {
        this.iterableDiffer = this.iterableDiffers.find([]).create(null);
    }

    ngDoCheck(): void {
        if (this.iterableDiffer.diff(this.messages)) {
            this.numberOfMessagesChanged = true;
        }
    }

    ngAfterViewChecked(): void {
        const isScrolledDown = Math.abs(this.scrollable.nativeElement.scrollHeight - this.scrollable.nativeElement.scrollTop - this.scrollable.nativeElement.clientHeight) <= 3.0;

        if (this.numberOfMessagesChanged && !isScrolledDown) {
            this.scrollToBottom();
            this.numberOfMessagesChanged = false;
        }
    }

    scrollToBottom() {
        try {
            this.scrollable.nativeElement.scrollTop = this.scrollable.nativeElement.scrollHeight;
        } catch (e) {
            console.error(e);
        }
    }

}

chat.component.html

<div class="chat-wrapper">

    <div class="chat-messages-holder" #scrollable>

        <app-chat-message *ngFor="let message of messages" [message]="message">
        </app-chat-message>

    </div>

    <div class="chat-input-holder">
        <app-chat-input (send)="onSend($event)"></app-chat-input>
    </div>

</div>

chat.component.sass

.chat-wrapper
  display: flex
  justify-content: center
  align-items: center
  flex-direction: column
  height: 100%

  .chat-messages-holder
    overflow-y: scroll !important
    overflow-x: hidden
    width: 100%
    height: 100%
2
RTYX

Vivekの答えは私のために働いていますが、それがエラーをチェックした後に表現が変化したという結果になりました。コメントはどれも私には効きませんでしたが、私がしたのは変更検出戦略を変更することでした。

import {  Component, ChangeDetectionStrategy } from '@angular/core';
@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  selector: 'page1',
  templateUrl: 'page1.html',
})
1
user3610386

材料設計sidenavを使用して角度で私は以下を使用する必要がありました:

let ele = document.getElementsByClassName('md-sidenav-content');
    let eleArray = <Element[]>Array.prototype.slice.call(ele);
    eleArray.map( val => {
        val.scrollTop = val.scrollHeight;
    });
0
Helzgate
const element = document.getElementById('box');
  element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });
0
Kanomdook