メッセージ送受信時にスクロール位置を最下部に維持する処理を実装【TypeScript/Vue.js】

サムネイル
※当ブログの記事にはアフィリエイトリンクを使用している場合があります。

概要

SlackやLINE、ChatGPTのようなチャットのUIでは最新のメッセージが最下部になっていることが多いです。

上記のUIでは新規のメッセージが追加されたとき、
元のスクロール位置が最下部 → 最下部を維持
元のスクロール位置が最下部以外 → 元のスクロール位置を維持
という仕様になっています。

動作イメージ

動作イメージ

動作イメージ

この機能をVue.jsで実装したので、実装方法を記載します。

完成品

Vue3(Composition API)で作成しました。

以下のリンク先でコードとプレビューを確認できます。

Vue-SFC-Playground

実装方法

以下の流れで実装します。

  1. メッセージエリアの作成
  2. 最下部へのスクロール
  3. 最下部にスクロールしているかの判定

メッセージエリアの作成

まずは、メッセージを表示する領域とメッセージを追加するためのボタンを用意します。

refオブジェクトを宣言したあと、操作したいDOM要素のref属性に宣言した変数を渡すとDOMにアクセスできるようになります。

最下部へのスクロール

次にメッセージエリアにメッセージが追加されたらスクロール位置を最下部に変更する処理を実装します。

MutationObserverchildList: trueとして呼び出すと、DOMに要素が追加されたことを検知できます。

scrollTo()を使うとスクロール位置を変更できます。
スクロール位置はtop:スクロールするピクセル数で指定するのですが、最大値を超えていたらスクロールできる最大値に設定されるため、ここでは仮に大きめな値を入れてあります。(後で正しい値に修正します。)

最下部にスクロールしているかの判定

最下部にスクロールしているかの判定に必要な変数を用意します。

clientHeightは見た目上の要素の高さです。
resizeObserverを使うことによってclientHeightの変更を検知します。

scrollHeightはoverflowしていて画面上に表示されない部分を含めた要素の中身の高さです。
MutationObserverを使うことによって、メッセージエリアに要素が追加されたときのscrollHeightの変更を検知します。

scrollTopは垂直方向にスクロールされている距離です。
scrollイベントが発生したときにscrollTopの変更を検知します。

次にスクロール位置が最下部になっているかの判定をします。

理論上はscrollHeight - clientHeight - scrollTop0のときにスクロール位置が最下部になります。
===で判定していないのは、scrollTopは小数を含む可能性があるのに対して、scrollHeightclientHeightは整数に丸められるため、スクロール量が閾値に十分に近いかで判定する必要があるからです。1

あとはメッセージ追加時にisScrollAtBottomがtrueのときだけスクロール位置を最下部にする処理を実行するようにします。

完成

全体のコードは以下のようになります。

最後にscrollTo({top: 10000})topの指定をscrollHeight - clientHeightとしました。

補足

DOMNodeInsertedについて

DOMの追加はDOMNodeInsertedでも検知できるようですが、こちらは現在deprecatedのようです。2

Intersection Observer APIについて

ここでは記載しませんが、仕様によってはIntersection Observer APIを使った実装もありかもしれません。

Intersection Observer APIを使って、ページの最下部までスクロールしたかを判定する

behaviorオプションについて

scrollTo()のオプションでbehavior: "smooth"とすると、スクロール動作が滑らかになります。

ただし、ChatGPTのような一文字ずつ文字が増加するUIの場合、スクロールが完了する前に次のスクロール位置判定処理が走ってしまい、スクロール位置は最下部に固定されない場合があります。

対策としては、スクロール判定の距離を短くしたり、文字の更新の間隔を長めにするなどが考えられます。
見た目にこだわらないのであれば、smoothオプションを付けないのが楽です。

参考

出典

Footnotes

  1. Element.scrollHeight | 要素が完全にスクロールされたかどうかの判定
  2. W3C | DOMNodeInserted

この記事をSNSで共有する

関連記事

オススメ記事

このブログを運営している人

アイコン

Webシステムエンジニア / DTMer

あっしゅからー

フリーランスでWebシステムエンジニアをやっています。

趣味ではDTMをしていて、オリジナル曲をニコニコ動画やYouTubeに投稿しています。

Copyright © 2024 あっしゅからー - All right reserved