メッセージ送受信時にスクロール位置を最下部に維持する処理を実装【TypeScript/Vue.js】
目次
概要
SlackやLINE、ChatGPTのようなチャットのUIでは最新のメッセージが最下部になっていることが多いです。
上記のUIでは新規のメッセージが追加されたとき、
元のスクロール位置が最下部 → 最下部を維持
元のスクロール位置が最下部以外 → 元のスクロール位置を維持
という仕様になっています。
動作イメージ
動作イメージ
この機能をVue.jsで実装したので、実装方法を記載します。
完成品
Vue3(Composition API)で作成しました。
以下のリンク先でコードとプレビューを確認できます。
実装方法
以下の流れで実装します。
- メッセージエリアの作成
- 最下部へのスクロール
- 最下部にスクロールしているかの判定
メッセージエリアの作成
まずは、メッセージを表示する領域とメッセージを追加するためのボタンを用意します。
refオブジェクトを宣言したあと、操作したいDOM要素のref
属性に宣言した変数を渡すとDOMにアクセスできるようになります。
最下部へのスクロール
次にメッセージエリアにメッセージが追加されたらスクロール位置を最下部に変更する処理を実装します。
MutationObserver
をchildList: true
として呼び出すと、DOMに要素が追加されたことを検知できます。
scrollTo()
を使うとスクロール位置を変更できます。
スクロール位置はtop:スクロールするピクセル数
で指定するのですが、最大値を超えていたらスクロールできる最大値に設定されるため、ここでは仮に大きめな値を入れてあります。(後で正しい値に修正します。)
最下部にスクロールしているかの判定
最下部にスクロールしているかの判定に必要な変数を用意します。
clientHeight
は見た目上の要素の高さです。resizeObserver
を使うことによってclientHeight
の変更を検知します。
scrollHeight
はoverflowしていて画面上に表示されない部分を含めた要素の中身の高さです。MutationObserver
を使うことによって、メッセージエリアに要素が追加されたときのscrollHeight
の変更を検知します。
scrollTop
は垂直方向にスクロールされている距離です。scroll
イベントが発生したときにscrollTop
の変更を検知します。
次にスクロール位置が最下部になっているかの判定をします。
理論上はscrollHeight - clientHeight - scrollTop
が0
のときにスクロール位置が最下部になります。===
で判定していないのは、scrollTop
は小数を含む可能性があるのに対して、scrollHeight
とclientHeight
は整数に丸められるため、スクロール量が閾値に十分に近いかで判定する必要があるからです。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
オプションを付けないのが楽です。
参考
- MDN Web Docs | Element.scrollTo()
- MDN Web Docs | Element.clientHeight
- MDN Web Docs | Element.scrollHeight
- MDN Web Docs | Element: scrollTop
- MDN Web Docs | scroll イベント
- MDN Web Docs | ResizeObserver
- MDN Web Docs | MutationObserver