現象
NuxtLink
の:to
を使用して画面内のナビゲーションを作成したが、一度項目を選択した後、画面をスクロールして別のセクションまで移動、その後もう一度同じ項目を選択すると、遷移が起きずスクロールした位置のままになってしまう。
期待値
再度選択した場合も、セクションに遷移したい。
原因
これはNuxtLink(Vue Router)の仕様らしい。
同じセクションをクリックしても遷移前とURLが変わらないため、遷移がスキップされてしまう。
解消方法
$router.push()
を使用する & nuxt.config.js
を編集することで解決できる。
が、今回nuxt.config.js
に手を加えたくなかったので、遷移ではないが、画面内スクロールを起こすことで解決してみました。
<template>
<NuxtLink
:key="link.to"
:to="link.to"
@click.native="handleClick($event, link.to)"
>
{{ link.label }}
</NuxtLink>
</template>
<script lang="ts">
methods: {
handleClick(event: MouseEvent, to: string) {
// すでに同じハッシュが指定されている場合はスクロールする
const hash = to.startsWith('#') ? to : to.split('#')[1] && `#${to.split('#')[1]}`
if (hash && window.location.hash === hash) {
event.preventDefault()
const el = document.querySelector(hash)
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
}
}
// それ以外はNuxtLinkのデフォルト動作
},
}
</script>
これで、同じセクションがクリックされた場合はスクロールで指定されたセクションを表示してくれるようになります。
解決方法2
前述した$router.push()
を使用する & nuxt.config.js
を編集する方法の場合
これはNuxt2限定のようです。Nuxt3の場合はapp/router.options.ts
とのこと
<template>
<!-- NuxtLinkは同じ -->
</template>
<script lang="ts">
methods: {
handleClick(event: MouseEvent, to: string) {
const current = this.$route.fullPath
const target = to.startsWith('#') ? `${this.$route.path}${to}` : to
if (current === target || (to.startsWith('#') && window.location.hash === to)) {
event.preventDefault()
// 強制的に遷移させる
this.$router.push(target)
}
// それ以外はNuxtLinkのデフォルト動作
},
},
</script>
// nuxt.config.js
export default {
// 他の設定は省略
router: {
scrollBehavior(to, from, savedPosition) {
if (to.hash) {
return new Promise((resolve) => {
// DOMが描画されるまで待つ
setTimeout(() => {
const el = document.querySelector(to.hash)
if (el) {
el.scrollIntoView({ behavior: 'smooth' })
resolve({ selector: to.hash })
} else {
resolve({ x: 0, y: 0 })
}
}, 300)
})
}
if (savedPosition) {
return savedPosition
}
return { x: 0, y: 0 }
}
}
}
この設定を加えると、リロードしても指定されたセクションまでスクロールしてくれます。
なので、$router.push()
で再度同じURLに遷移すると、指定されたセクションを表示してくれるというわけですね。
おわりに
vue-routerさんは便利だけど、デフォルトでよしなにやってくれないこともあるので要注意ですね。