🍕

Nuxt - スクロール処理の設定について

2023/01/20に公開

はじめに

スクロールの振る舞いを制御したい時に以下のファイルを修正します。
 app/router.scrollBehavior.js

このファイルは、プロジェクトルートにあるappフォルダー配下に置く必要があります。

Nuxtにデフォルトで入っているこのファイルの設定は以下のページから閲覧できます。
https://github.com/nuxt/nuxt/blob/2.x-dev/packages/vue-app/template/router.scrollBehavior.js

あるプロジェクトでこのファイルを触ったときに理解に苦しんだのでzennに記事を書きました。

router.scrollBehavior.jsとは

冒頭でも書きましたが、ルーティングに関する処理でスクロールの振る舞いを変えたいときにこのファイルを触ります。

ファイルの中で関数をdefault exportすることで、その設定が適応されます。
実はこのファイルはvue-routerが元となっています↓
https://router.vuejs.org/guide/advanced/scroll-behavior.html

何に苦しんだの?

そもそもプロジェクトで実現したかったこととしては

  • 同じページ内の遷移は、smoothスクロール
  • 違うページに遷移後は、該当箇所へ通常のスクロール
    でした。

そこで最初に書いたコードはこちら

export default function (to, from, savedPosition) {
  let position = false
  if (savedPosition) {
    position = savedPosition
  } else if (to !== from) {
    position = { x: 0, y: 0 }
  }

  // ページ内ではsmoothスクロール
  if (to.hash && to.path === from.path) {
    const element = document.querySelector(to.hash)
    if (element) {
      return window.scrollTo({
        top: element.offsetTop,
        behavior: 'smooth',
      })
    }
  }

  // ページ遷移した際は通常のスクロール
  let offset = {}
  if (to.hash && to.path === from.path) {
    position = {
      selector: to.hash,
      behavior: 'auto',
      offset,
    }
    return position
  }
}

しかしこれではうまくいきませんでした。想定していた箇所へ遷移してくれないのです。

原因は、ページ遷移した際、ページが生成される前にスクロール処理を実行したらスクロールが失敗するというものでした。

どういうことか

ページ内遷移では、既にページのDOMが生成されスクロールの処理がいつでも正常に動きます。

しかし、ページ遷移する際は「ページのDOMが生成 → jsがバインドされる → スクロール処理が正常に動く」と、ページ内と比べてスクロール処理が正常に動くまでラグがあります。

そのため、スクロール処理が正常に動かない間にスクロールしようとした際に失敗していたのです。

どう解決したか

以下のコードへ修正しました

export default function (to, from, savedPosition) {
  let position = false
  if (savedPosition) {
    position = savedPosition
  } else if (to !== from) {
    position = { x: 0, y: 0 }
  }

  // ページ内ではsmoothスクロール
  if (to.hash && to.path === from.path) {
    const element = document.querySelector(to.hash)
    if (element) {
      return window.scrollTo({
        top: element.offsetTop,
        behavior: 'smooth',
      })
    }
  }

  // ページ遷移した際は通常のスクロール
  // `triggerScroll`イベントがemitされた際に実行される※`triggerScroll`イベントとは、スクロール処理が動作可能になったことを知らせるイベントです
  if (to.hash && to.path !== from.path) {
    return new Promise((resolve) => {
      this.app.$root.$once('triggerScroll', () => {
        position = {
          selector: to.hash,
          behavior: 'auto',
        }
        resolve(position)
      })
    })
  }
}

ちなみに、triggerScrollイベント内ではnextTickが使われているそうです

参考記事

こちらは、本内容を理解するのにかなり役に立ちました!
https://qiita.com/yuta-katayama-23/items/6c3dd3f79dc675db2abb

Discussion