はじめに
スクロールの振る舞いを制御したい時に以下のファイルを修正します。
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