Hi there
I’m building a simple chat application and I’m facing an issue with setting the scroll position in iOS. It seems to work fine on other platforms, including browser. Ionic v5, using Capacitor, building with Appflow.
The container needs to be scrolled to the bottom by default when the data loads. The page then listens for new messages. If a new message is added then the page should scroll to the bottom if the scroll position is within the bottom 150% of the client height. If not, a FAB appears indicating a new message has appeared and the user can select the button to scroll to the button.
The markup is as so. I have a custom domChange directive which listens to dom changes in the div. I prefer to put the scroll logic here as I was experiencing bugs putting it in the message listener before.
<ion-content>
<div class="chat-container" (click)="containerClick();" (scroll)="scroll($event)" #chatContainer>
<div class="wrapper" *ngIf="messages.length > 0" (domChange)="onDomChange($event)">
<!-- Excuse my inline styling, this is just for debug -->
<div style="width:100%;text-align:center;padding:20px;" *ngIf="loadingMore"><ion-spinner></ion-spinner></div>
<ng-container *ngFor="let message of messages">
<chat-bubble
[sent]="message.from === authService.user.id"
[delivered]="message.delivered"
[position]="message.position"
[timestamp]="message.createdAt.getTime()"
[content]="message.content"
>
</chat-bubble>
</ng-container>
</div>
</div>
<ion-fab vertical="bottom" horizontal="center" slot="fixed" *ngIf="showScrollBtn">
<ion-fab-button size="small" (click)="scrollToBottom()"><ion-icon name="arrow-down-outline"></ion-icon></ion-fab-button>
</ion-fab>
</ion-content>
The CSS is relatively simple but I am use a column-reverse flexbox and I think this might be one of the causes to the issue. The column-reverse means the webview renders the scroll at the bottom by default.
.chat-container {
height: 100%;
flex-grow: 1;
overflow: auto;
display: flex;
flex-direction: column-reverse;
overflow-x: hidden;
padding: 0 16px 0 16px;
}
And then the relevant functions are:
public onDomChange(event: any) {
if(event.addedNodes.length > 0 && event.addedNodes[0].localName == 'chat-bubble') {
const scrollTop = (this.platform.is('ios') && !this.platform.is('mobileweb')) ? this.chatContainer.nativeElement.scrollTop + this.chatContainer.nativeElement.scrollHeight : this.chatContainer.nativeElement.scrollTop;
/*
* Because of the column-reverse iOS sees the bottom of the div as scrollTop = 0 and the top of the div as scrollTop = -scrollHeight
* Android sees it as scrollTop = scrollHeight at the bottom and scrollTop = 0 at the top
*/
const scrollHeight = this.chatContainer.nativeElement.scrollHeight;
const clientHeight = this.chatContainer.nativeElement.clientHeight * 1.5;
const newMessageIndex = this.messages[this.messages.length - 1].index;
if((this.messages.length <= 30 || scrollHeight - scrollTop <= clientHeight) && this.channel.lastConsumedMessageIndex < newMessageIndex) {
this.scrollToBottom();
} else if(this.channel.lastConsumedMessageIndex < newMessageIndex) {
this.showScrollBtn = true;
}
}
}
public scrollToBottom(): Promise<any> {
this.showScrollBtn = false;
this.chatContainer.nativeElement.scrollTop = this.chatContainer.nativeElement.scrollHeight;
return this.channel.setAllMessagesConsumed();
}
The scrollToBottom()
function works absolutely fine when triggered by the button click event but doesn’t seem to do anything when triggered in the domChange trigger. I think this is because the trigger is called before the chat bubble element has rendered but I’m not sure how to fix this.
Would appreciate any ideas/thoughts
Thanks in advance