[Nuxt.js] アンカーを設定した際のスクロールの挙動について
Nuxt.jsでページ遷移する際に、アンカーを設定するとscrollBehavior が働いて指定の位置までスクロール位置を移動させてくれます。
デフォルトだと👇の設定になってるようです。
const scrollBehavior = function (to, from, savedPosition) {
let position = false
if (to.matched.length < 2) {
position = { x: 0, y: 0 }
} else if (to.matched.some((r) => r.components.default.options.scrollToTop)) {
position = { x: 0, y: 0 }
}
if (savedPosition) {
position = savedPosition
}
return new Promise(resolve => {
window.$nuxt.$once('triggerScroll', () => {
if (to.hash && document.querySelector(to.hash)) {
position = { selector: to.hash }
}
resolve(position)
})
})
}
ただデフォルトのままで、アンカーリンクでページ遷移しようとすると3点問題がありました。
-
固定ヘッダーがある場合、ヘッダーの高さ分表示位置がズレてしまう問題
-
ブラウザバックすると、アンカーの位置までスクロールが戻ってしまう問題
-
ブラウザバックすると、
triggerScroll
の発火が早すぎて、ページ遷移前にスクロールが発生してしまう問題
1,2はうまく回避😆
3はぎりぎり回避…😔
1. 固定ヘッダーがある場合、ヘッダーの高さ分表示位置がズレてしまう問題
scrollBehavior
は、以下のうちの1つのスクロールポジションオブジェクトを返します。
-
{ x: number, y: number }
-
{ selector: string }
-
{ selector: string, offset? : { x: number, y: number }}
とのことです。
アンカーの位置を取得しているのはここ👇
if (to.hash && document.querySelector(to.hash)) {
position = { selector: to.hash }
}
3番目の形式を使って、offset
を設定して回避できます。
if (to.hash && document.querySelector(to.hash)) {
position = { selector: to.hash, offset: { x: 0, y: (ヘッダーの高さ) } }
}
2.ブラウザバックすると、アンカーの位置までスクロールが戻ってしまう問題
ブラウザバックした際に、scrollBehavior
の第3引数「savedPosition
」は、前のページのスクロールした位置をオブジェクトで返します。
前ページのスクロール位置を設定しているのここ👇
if (savedPosition) {
position = savedPosition
}
savedPosition
をセットしたどこまでいいのだが、ブラウザバックした時のURLには、アンカーが設定されているため、先程のアンカーの位置の取得する箇所で position
が上書きされてしまいます…😱
そのため、ブラウザバックしても前回のスクロール位置にスクロールせずに、アンカーの位置にスクロールが戻ってしまっています。
アンカーの位置を取得する条件文を変えて回避しました。
if (to.hash && document.querySelector(to.hash)) {
...
}
↓
if (to.hash && document.querySelector(to.hash) && !savedPosition) {
...
}
3. ブラウザバックすると、triggerScrollの発火が早すぎて、ページ遷移前にスクロールが発生してしまう問題
「絶対同じようにハマってるやついるだろ😡」と思ってたら、それっぽいIssuesを見つけました!
💡
I figured out that the triggerScroll seems to be fired too early, the page/component isn’t loaded yet. I solved it for now with a timeout but not sure if thats a good way.
タイムアウトを設定して、発火を遅らせることで回避しました。
無理やり感ある…。
window.$nuxt.$once('triggerScroll', () => {
.
.
.
resolve(position)
})
↓
window.$nuxt.$once('triggerScroll', () => {
.
.
.
setTimeout(() => { resolve(position) }, 200);
})
他のいい方法あったら共有して欲しいです…
最終的に
nuxt.config.js
でscrollBehavior
を上書きして終了。
router: {
scrollBehavior(to, from, savedPosition) {
let position = false
if (to.matched.length < 2) {
position = { x: 0, y: 0 }
} else if (to.matched.some((r) => r.components.default.options.scrollToTop)) {
position = { x: 0, y: 0 }
}
if (savedPosition) {
position = savedPosition
}
return new Promise(resolve => {
window.$nuxt.$once('triggerScroll', () => {
if (to.hash && document.querySelector(to.hash) && !savedPosition) {
position = { selector: to.hash, offset: { x: 0, y: (ヘッダーの高さ) } }
}
setTimeout(() => { resolve(position) }, 200);
});
});
}
}