Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
20
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

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

対象

  • 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は結構便利なんじゃないだろうか

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
20
Help us understand the problem. What are the problem?