कोणीय 2 नीचे तक स्क्रॉल करें (चैट शैली)


111

मेरे पास एक ng-forलूप के भीतर एकल कोशिका घटकों का एक सेट है ।

मेरे पास सब कुछ है, लेकिन मुझे उचित पता नहीं लग सकता है

वर्तमान में मेरे पास है

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

लेकिन यह हर समय काम नहीं करता है क्योंकि छवियाँ एसिंक्रोनस रूप से व्यूपोर्ट को नीचे धकेलती हैं।

कोणीय 2 में चैट विंडो के नीचे स्क्रॉल करने का उपयुक्त तरीका है?

जवाबों:


203

मुझे एक ही समस्या थी, मैं AfterViewCheckedऔर @ViewChildसंयोजन (Angular2 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()आपको ठीक होना चाहिए।


मेरे पास एक होम कंपोनेंट है, होम टेम्प्लेट में मेरे पास div में एक और कंपोनेंट है जो डेटा दिखा रहा है और डेटा को जोड़े रखता है लेकिन स्क्रॉल बार css होम टेम्प्लेट div में नहीं अंदर के टेम्प्लेट में है। समस्या यह है कि इस स्क्रॉलटबॉटम कॉलिंग को कॉल किया जाता है। हर डेटा घटक के अंदर आता है, लेकिन #scrollMe होम कंपोनेंट में है, क्योंकि div स्क्रॉल करने योग्य है ... और # स्क्रॉलमे अंदर के कंपोनेंट से एक्सेस करने योग्य नहीं है .. निश्चित नहीं है कि #scrollme का उपयोग किस प्रकार से किया जाए
Anagh वर्मा

आपका समाधान, @Basit, सुंदर रूप से सरल है और उपयोगकर्ता द्वारा मैन्युअल रूप से स्क्रॉल करने पर अंतहीन स्क्रॉलिंग को समाप्त करने की आवश्यकता नहीं होती है।
theotherdy

3
नमस्ते, क्या उपयोगकर्ता द्वारा स्क्रॉल किए जाने पर स्क्रॉल को रोकने का कोई तरीका है?
जॉन लुई डेला क्रूज़

2
मैं चैट शैली के दृष्टिकोण में इसका उपयोग करने की कोशिश कर रहा हूं, लेकिन उपयोगकर्ता को स्क्रॉल करने पर इसे स्क्रॉल करने की आवश्यकता नहीं है। मैं देख रहा हूँ और यह पता लगाने का कोई तरीका नहीं खोज सकता कि क्या उपयोगकर्ता ने स्क्रॉल किया है। एक चेतावनी यह है कि अधिकांश समय यह घटक पूर्ण स्क्रीन नहीं है और इसकी अपनी स्क्रॉल पट्टी है।
हरविप

2
@HarvP और JohnLouieDelaCruz क्या आपको स्क्रॉल छोड़ने का कोई हल मिला है यदि उपयोगकर्ता मैन्युअल रूप से स्क्रॉल करता है?
सुहेलव्स

166

इसके लिए सबसे सरल और सबसे अच्छा उपाय है:

इस #scrollMe [scrollTop]="scrollMe.scrollHeight"साधारण सी चीज़ को टेम्पलेट की तरफ से जोड़ें

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

यहाँ काम करने वाले डेमो (डमी चैट ऐप के साथ) और पूर्ण कोड के लिए लिंक है

Angular2 के साथ काम करेंगे और 5 तक भी, जैसा कि ऊपर डेमो Angular5 में किया गया है।


ध्यान दें :

त्रुटि के लिए: ExpressionChangedAfterItHasBeenCheckedError

कृपया अपने सीएसएस की जांच करें, यह सीएसएस पक्ष का मुद्दा है, न कि कोणीय पक्ष, उपयोगकर्ता @KHAN में से एक ने इसे हटाकर हल कर दिया overflow:auto; height: 100%;है div। (कृपया विस्तार से बातचीत की जाँच करें)


3
और आप स्क्रॉल को कैसे ट्रिगर करते हैं?
बुर्जुआ

3
यह किसी भी आइटम पर नीचे की ओर आने-जाने के लिए जोड़ा जाएगा या जब आंतरिक तरफ div की ऊँचाई बढ़ेगा
विवेक

2
Thx @VivekDoshi। हालांकि कुछ जोड़े जाने के बाद मुझे यह त्रुटि मिलती है:> त्रुटि त्रुटि: अभिव्यक्तिचार्जित। इसके बादहैसबैनचेक किए गए त्रुटि: इसकी जांच के बाद अभिव्यक्ति बदल गई है। पिछला मान: '700'। वर्तमान मूल्य: '517'।
वासिलिस गड्ढे

6
@csharpnewbie सूची से कोई भी संदेश निकालते समय मेरे पास एक ही मुद्दा है Expression has changed after it was checked. Previous value: 'scrollTop: 1758'. Current value: 'scrollTop: 1734':। क्या आपने इसे हल किया?
एंड्री

6
मैं "जांच के बाद बदल गया है" अभिव्यक्ति को हल करने में सक्षम था "(ऊंचाई को बनाए रखते हुए: 100%; अतिप्रवाह-वाई: स्क्रॉल) [स्क्रॉलटॉप] में एक शर्त जोड़कर, इसे UNTIL सेट होने से रोकने के लिए मेरे पास पॉप्युलेट करने के लिए डेटा था। इसके साथ, पूर्व: <ul class = "chat-history" #chatHistory [scrollTop] = "messages.ll === = 0? 0: chatHistory.scrollHeight"> <li * ngFor = "संदेशों का संदेश दें"> .. "मेसेज.लिफ्टिंग === 0? 0" चेक के अलावा समस्या को ठीक करने के लिए लग रहा था, हालांकि मैं समझा नहीं सकता, इसलिए मैं निश्चित रूप से यह नहीं कह सकता कि फिक्स मेरे कार्यान्वयन के लिए अद्वितीय नहीं है।
बेन

20

मैंने यह देखने के लिए एक चेक जोड़ा कि क्या उपयोगकर्ता ने स्क्रॉल करने की कोशिश की है।

मैं इसे यहाँ छोड़ने जा रहा हूँ अगर कोई इसे चाहता है :)

<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) { }
    }

}

कंसोल में त्रुटियों के बिना वास्तव में व्यावहारिक समाधान। धन्यवाद!
दिमित्री वासिलुक जस्ट 3 एफ

20

संदेशों के माध्यम से स्क्रॉल करते समय स्वीकृत उत्तर आग देता है, इससे बचा जाता है।

आप इस तरह से एक टेम्पलेट चाहते हैं।

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

फिर आप पृष्ठ पर जोड़े जा रहे नए संदेश तत्वों की सदस्यता के लिए एक ViewChildren एनोटेशन का उपयोग करना चाहते हैं।

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

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

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

हाय - इस दृष्टिकोण की कोशिश कर रहा हूँ, लेकिन यह हमेशा पकड़ ब्लॉक में चला जाता है। मेरे पास एक कार्ड है और उसके अंदर मेरे पास एक div है जो कई संदेश प्रदर्शित करता है (कार्ड div के अंदर, मैं आपकी संरचना के समान है)। अगर यह css या ts फ़ाइलों में किसी अन्य परिवर्तन की आवश्यकता है, तो क्या आप मुझे समझने में मदद कर सकते हैं? धन्यवाद।
csharpnewbie

2
अब तक का सबसे अच्छा जवाब। धन्यवाद !
कैग्लियोस्त्रो

1
यह वर्तमान में एक कोणीय बग के कारण काम नहीं करता है, जहां QueryList ने केवल एक बार भी फायरिंग में सदस्यता बदल दी है, जबकि ngAfterViewit में घोषित किया गया है। Mert द्वारा समाधान केवल एक ही है जो काम करता है। विवेक के समाधान से कोई फर्क नहीं पड़ता कि किस तत्व को जोड़ा जाता है, जबकि मर्ट केवल आग लगा सकता है जब कुछ तत्व जोड़े जाते हैं।
जॉन हैम

1
यह एकमात्र उत्तर है जो मेरी समस्या को हल करता है। कोणीय 6 के साथ काम करता है
सुहैलव्स

2
बहुत बढ़िया!!! मैंने ऊपर एक का उपयोग किया और सोचा कि यह अद्भुत था, लेकिन फिर मैं नीचे स्क्रॉल करना बंद कर दिया और मेरे उपयोगकर्ता पिछली टिप्पणियों को नहीं पढ़ सके! इस समाधान के लिए धन्यवाद! बहुत खुबस! अगर मैं कर सकता तो मैं आपको 100+ अंक देता: -D
cheesydoritosandkale

19

का उपयोग करने पर विचार करें

.scrollIntoView()

Https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView देखें


अभी तक एक मानक नहीं है, यदि आपके पास क्रॉसबोवर संगतता है तो आपके पास सीमाएं होंगी।
चौकोरलोस

1
@chavocarlos - ओपेरा मिनी को छोड़कर हर चीज का समर्थन करता प्रतीत होता है: caniuse.com/#feat=scrollintoview
बोरिस याकुबचिक

13

यदि आप सुनिश्चित करना चाहते हैं, कि आप * 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;
}

यहां महत्वपूर्ण, "अंतिम" चर परिभाषित करता है यदि आप वर्तमान में अंतिम आइटम पर हैं, तो आप "स्क्रॉलटॉब" विधि को ट्रिगर कर सकते हैं


1
यह उत्तर स्वीकार किए जाने योग्य है। यह एकमात्र समाधान है जो काम करता है। Denixtry का समाधान एक कोणीय बग के कारण काम नहीं करता है, जहाँ QueryList ने केवल एक बार फायरिंग में सदस्यता ले ली, जबकि ngAfterViewInit में घोषित किया गया। विवेक के समाधान से कोई फर्क नहीं पड़ता कि किस तत्व को जोड़ा जाता है, जबकि मर्ट केवल आग लगा सकता है जब कुछ तत्व जोड़े जाते हैं।
जॉन हैम

धन्यवाद! अन्य विधियों में से प्रत्येक में कुछ डाउनसाइड हैं। यह बस के रूप में यह इरादा है काम करता है। अपना कोड साझा करने के लिए धन्यवाद!
lkaupp

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

5
किसी भी उत्तर को पूछने वाले को सही दिशा में जाने में मदद मिलती है, लेकिन अपने उत्तर में किसी भी सीमा, धारणा या सरलीकरण का उल्लेख करने का प्रयास करें। ब्रेवीटी स्वीकार्य है, लेकिन फुलर स्पष्टीकरण बेहतर हैं।
बैडुकर

2

अपना समाधान साझा कर रहा हूं, क्योंकि मैं बाकी लोगों से पूरी तरह संतुष्ट नहीं था। इसके साथ मेरी समस्या AfterViewCheckedयह है कि कभी-कभी मैं स्क्रॉल कर रहा हूं, और किसी कारण से, यह जीवन हुक कहा जाता है और यह मुझे नीचे स्क्रॉल करता है, भले ही कोई नया संदेश न हो। मैंने उपयोग करने की कोशिश की OnChangesलेकिन यह एक मुद्दा था, जिसने मुझे इस समाधान की ओर अग्रसर किया । दुर्भाग्य से, केवल उपयोग करने से, DoCheckयह संदेश दिए जाने से पहले स्क्रॉल कर रहा था, जो या तो उपयोगी नहीं था, इसलिए मैंने उन्हें जोड़ दिया ताकि DoCheck मूल रूप से संकेत दे रहा है AfterViewCheckedकि क्या यह कॉल करना चाहिए scrollToBottom

प्रतिक्रिया पाकर खुशी हुई।

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%

इसका उपयोग यह जांचने के लिए किया जा सकता है कि क्या DOM में नया संदेश जोड़ने से पहले चैट को नीचे स्क्रॉल किया गया है: const isScrolledDown = Math.abs(this.scrollable.nativeElement.scrollHeight - this.scrollable.nativeElement.scrollTop - this.scrollable.nativeElement.clientHeight) <= 3.0;(जहां पिक्सेल में 3.0 सहिष्णुता है)। यह ngDoCheckसशर्त रूप से सेट नहीं किया जा सकता shouldScrollDownहै trueयदि उपयोगकर्ता मैन्युअल रूप से स्क्रॉल किया गया है।
रुस्लान Stelmachenko

सुझाव के लिए धन्यवाद @RuslanStelmachenko मैंने इसे लागू किया (मैंने इसे ngAfterViewCheckedअन्य बूलियन के साथ करने का फैसला किया । मैंने इस बूलियन को संदर्भित करने के बारे में कुछ स्पष्टता देने के लिए shouldScrollDownनाम बदल दिया numberOfMessagesChanged
RTYX

मैं आपको देखता हूं if (this.numberOfMessagesChanged && !isScrolledDown), लेकिन मुझे लगता है कि आप मेरे सुझाव के इरादे को नहीं समझ पाए। मेरे असली इरादा है नहीं स्वचालित रूप से नीचे स्क्रॉल भी जब नया संदेश, जोड़ा जाता है, तो उपयोगकर्ता मैन्युअल रूप से ऊपर स्क्रॉल किया। इसके बिना, यदि आप इतिहास को देखने के लिए स्क्रॉल करते हैं, तो नया संदेश आते ही चैट स्क्रॉल डाउन हो जाएगा। यह बहुत कष्टप्रद व्यवहार हो सकता है। :) तो, मेरा सुझाव यह जांचने के लिए था कि क्या डोम में नए संदेश को जोड़ने से पहले चैट को स्क्रॉल किया गया है और यदि यह है तो ऑटोस्क्रोल नहीं। ngAfterViewCheckedबहुत देर हो चुकी है क्योंकि DOM पहले से बदल चुका है।
रुस्लान Stelmachenko

आआआआआआआआआआआआआआआआआअ,, मैं तो इरादा नहीं समझ रहा था। आपने जो कहा है वह समझ में आता है - मुझे लगता है कि उस स्थिति में कई चैट बस नीचे जाने के लिए एक बटन जोड़ते हैं, या एक निशान जो वहाँ एक नया संदेश है, इसलिए यह निश्चित रूप से इसे प्राप्त करने का एक अच्छा तरीका होगा। मैं इसे संपादित करूँगा और बाद में इसे बदल दूँगा। प्रतिक्रिया के लिए धन्यवाद!
RTYX

@ शेयर समाधान के साथ, आपके हिस्से के लिए धन्यवाद भगवान, मेरा विचार दुर्लभ मामलों (कॉल बैकेंड से खारिज कर दिया गया) पर लात मारी जाती है, आपके IterableDiffers के साथ यह अच्छी तरह से कार्य करता है। आपको बहुत - बहुत धन्यवाद!
लुकाप्प

2

विवेक के जवाब ने मेरे लिए काम किया है, लेकिन त्रुटि की जाँच के बाद एक अभिव्यक्ति बदल गई है। किसी भी टिप्पणी ने मेरे लिए काम नहीं किया, लेकिन मैंने जो किया वह परिवर्तन का पता लगाने की रणनीति थी।

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

1

सामग्री डिजाइन sidenav का उपयोग करते हुए कोणीय में मुझे निम्नलिखित का उपयोग करना पड़ा:

let ele = document.getElementsByClassName('md-sidenav-content');
    let eleArray = <Element[]>Array.prototype.slice.call(ele);
    eleArray.map( val => {
        val.scrollTop = val.scrollHeight;
    });

1
const element = document.getElementById('box');
element.scrollIntoView({ behavior: 'smooth', block: 'end', inline: 'nearest' });

0

अन्य समाधानों को पढ़ने के बाद, सबसे अच्छा समाधान जिसके बारे में मैं सोच सकता हूं, इसलिए आप केवल वही चलाएं जिसकी आपको आवश्यकता है, निम्नलिखित हैं: आप उचित बदलाव का पता लगाने के लिए ngOnChanges का उपयोग करते हैं

ngOnChanges() {
  if (changes.messages) {
      let chng = changes.messages;
      let cur  = chng.currentValue;
      let prev = chng.previousValue;
      if(cur && prev) {
        // lazy load case
        if (cur[0].id != prev[0].id) {
          this.lazyLoadHappened = true;
        }
        // new message
        if (cur[cur.length -1].id != prev[prev.length -1].id) {
          this.newMessageHappened = true;
        }
      }
    }

}

और आप इसका उपयोग करने से पहले वास्तव में परिवर्तन लागू करने के लिए ngAfterViewChecked का उपयोग करते हैं लेकिन पूरी ऊंचाई की गणना के बाद

ngAfterViewChecked(): void {
    if(this.newMessageHappened) {
      this.scrollToBottom();
      this.newMessageHappened = false;
    }
    else if(this.lazyLoadHappened) {
      // keep the same scroll
      this.lazyLoadHappened = false
    }
  }

यदि आप सोच रहे हैं कि स्क्रोलटबॉटम कैसे लागू किया जाए

@ViewChild('scrollWrapper') private scrollWrapper: ElementRef;
scrollToBottom(){
    try {
      this.scrollWrapper.nativeElement.scrollTop = this.scrollWrapper.nativeElement.scrollHeight;
    } catch(err) { }
  }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.