LoginSignup
32
23

More than 1 year has passed since last update.

nuxtで非同期で取得したデータに合わせてチャットを一番下までスクロールする

Last updated at Posted at 2019-06-26

対象

  • nxutを使ってLINEやmessengerのようなチャットを作っている人
  • (非同期で取得したデータを使ってDOMが更新された後のイベントをフックしたい人)

やりたいこと

  • チャット画面を開いた時点で、メッセージの一番下までスクロールしたい(以下この画面のことをチャットコンポーネントと呼ぶ)
  • メッセージはチャットコンポーネントを表示するタイミングで非同期でサーバから取得する(先に読み込んでおくとかはなし)

とりあえずチャットの一番下までスクロールする方法

このやり方がベストかどうかはわからないが、とりあえず以下のコードでできる(templata部分は省略。とりあえずoverflow: scrollになっているなにかしら)

scroll.vue
<script>
method: {
    scrollToEnd() {
          const chatLog = this.$refs.'スクロールしたいref属性'
          if (!chatLog) return
          chatLog.scrollTop = chatLog.scrollHeight
        }
    }
}
</script>

解説

  • this.$refs.'スクロールしたいref属性'でスクロールさせたいHTML要素を取得して、それのTOPをそれの高さにしてあげることで、一番したまでスクロールした状態にしているだけ https://jp.vuejs.org/v2/api/#ref

問題点

チャットコンポーネントが表示されたタイミングで一番したまでスクロールして欲しい。
しかしチャットコンポーネントに表示するメッセージはコンポーネントを表示するタイミングで非同期で取得するため、Vueのライフサイクルのmountedcreatedではまだ取得できていない。
(ライフサイクルに関してはここを→Vue インスタンス

試したこと

updatedを使う

非同期通信が終わったタイミングではbeforeUpadateupdatedが呼ばれる
DOMが更新され終わったタイミングでないとchatLog.scrollTopが取得できないため、使うとしたらupdated
つまりこういうこと↓

scroll.vue
<script>
updated() {
    this.scrollToEnd()
},
methods: {
    scrollToEnd() {
          const chatLog = this.$refs.'スクロールしたいref属性'
          if (!chatLog) return
          chatLog.scrollTop = chatLog.scrollHeight
        }
    }
}
</script>

しかし、updatedはメッセージ以外のpropsやdataが変わったタイミングでも呼ばれてしまうため、予期せぬタイミングでscrollToEnd()が呼ばれてしまう。 これはよくない

カスタムウォッチャを使う

メッセージの変更のみを拾いたいならカスタムウォッチャを使えばいい。
つまりこういうこと↓

scroll.vue
<script>
watch: {
    messages: function(newValue) {
        this.scrollToEnd()
    }
  },
methods: {
    scrollToEnd() {
          const chatLog = this.$refs.'スクロールしたいref属性'
          if (!chatLog) return
          chatLog.scrollTop = chatLog.scrollHeight
        }
    }
}
</script>

しかし、カスタムウォッチャでメッセージの変更を拾ったとしても、DOMが変更されるのはその後であり、chatLog.scrollTopが取得できないためこれもうまくいかない

結論

Vue.nextTick( [callback, context] )をつかう
Vue公式ドキュメント
nuxtのドキュメントでもひっそり登場している

これはcallbaclで与えた処理をDOMの更新が終わるまで待つというもの。
カスタムウォッチャでnextTickを呼び、その中でscrollToEnd()呼ぶことで、メッセージが非同期で更新され、DOMの表示が終わったタイミングでメッセージの一番したまでスクロールすることができる

最終的にこういうこと↓

scroll.vue
<script>
updated() {
    this.scrollToEnd()
},
methods: {
    scrollToEnd() {
          this.$nextTick(() => {
              const chatLog = this.$refs.'スクロールしたいref属性'
              if (!chatLog) return
              chatLog.scrollTop = chatLog.scrollHeight
           })
        }
    }
}
</script>

終わりに

propsやdataが更新されたタイミングで何かをしたいというのはよくあるが、DOMが更新されたあとに何かをしたいというのは初めてだったため、結構悩んだ。
しかし、DOMの更新後を拾いたいタイミングはこれからも結構ありそうなのでnextTickは結構便利なんじゃないだろうか

Twitterもやってるので、よければフォローお願いします。
https://twitter.com/ObataGenta

32
23
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
32
23