logo
Search
Programming

Nuxt.jsのページ遷移時のスクロール制御したい

#Nuxt.js
Oct 28th 2021 Oct 28th 2021
Nuxt.jsのページ遷移時のスクロール制御したい

Nuxt.jsのデフォルト設定では、ページ遷移時にスクロール位置が保持されたままページが遷移されてしまいます。こちらの挙動を制御するには、いくつか方法がありますが、今回は公式ドキュメントにもあるように、router.scrollBehavior.jsを作成して対応していきます。

スクロールの挙動を変更

スクロールの挙動を変更するには、基本的に以下の2つの方法があります。

  • nuxt.config.js内のroute.scrollBehaviorプロパティ
  • router.scrollBehavior.js (v2.9.0 ~ )

公式的には、router.scrollBehavior.jsを作成した方法を推奨しているようなので、今回はそちらの方法で設定を行っていきます。

※これらの設定は、Nuxt.jsが既に定義してあるデフォルトの設定を上書きする形になるので、注意が必要です。

router.scrollBehavior.jsの作成

プロジェクトのルートディレクトリ配下にappディレクトリを作成して、その中にrouter.scrollBehavior.jsを配置していきます。

// app/router.scrollBehavior.js
export default (to, from, savedPosition) => {
	return { x: 0, y: 0 }
}

このメソッドは、ページがレンダリングされる度に呼ばれます。メソッドの詳しい説明については、公式ドキュメントを参照してください。

Vue-Router-async-scrolling

単純にページ遷移時にスクロール位置を先頭にするには、上述したようにreturn { x: 0, y: 0 }を返却するだけでいいので、特にこだわりが無ければこれだけでも問題ないようにも思えます。

逆に固定レイアウトがある場合や、スムーススクロールを実装したい場合は少しこのファイルに手を加えてあげる必要があります。

// app/router.scrollBehavior.js
export default (to, from, savedPosition) => {
	return {
		selecter: to.hash,
		offset: { x: 0, y: 0 },
		behavior: 'smooth', // スムーススクロールの設定
	}
}

特定のアンカータグの遷移

Headless CMSから取得したコンテンツを利用する場合、nuxt-linkが使用できず普通のアンカータグになってしまうかと思います。この場合、アンカータグデフォルトの動作となってしまいscrollBehaviorがうまく動作してくれないことがあります。

この場合は、アンカータグのデフォルトの挙動を宣言して、vue-routerで遷移させることで解決します。

例えば、記事の目次としてこのようなアンカータグが存在するとします。

<div class="toc">
	<a href="#h1">H1</a>
	<a href="#h2">H2</a>
	<a href="#h3">H3</a>
</div>

これらのアンカータグに対して、preventDefault()を呼んであげて、その後vue-routerのpush()を呼んでページを遷移するといった形になります。

setup () {
	const router = useRouter()
	onMounted(() => {
		const tocs = document.querySelectorAll('.toc a[href^="#"]')
		for (let idx = 0; idx < tocs.length; i++) {
			tocs[idx].addEventListener('click', e => {
				e.preventDefault()
				router.push({ path: e.target.hash })
			})
		}
	})
}

エラー発生時の対応

DOMException: Failed to execute 'querySelector' on 'Document': '~~~' is not a valid selector.

日本語のハッシュをpushしたときに発生するエラーだと思います。パラメータの%E3とかが解決できなくて起きている模様ですね。こちらは、Nuxt.jsのデフォルトの設定を参考にhashをエスケープすることで解決することが可能です。

const hash = (() => {
  if (typeof window.CSS !== 'undefined' && typeof window.CSS.escape !== 'undefined')
    return `#${window.CSS.escape(to.hash.substr(1))}`
  return to.hash
})()

return {
	selector: hash,
}

TypeError: Cannot read properties of undefined (reading 'behavior')

デフォルトの設定を参考に色々触っていた際に、scrollBehaviorにasyncをくっつけていた時に発生したエラーです。

export default async (to, from, savedPosition) => {
	// ...
}

下記が、Vue-Routerのソースです。

router.app.$nextTick(function () {
  var position = getScrollPosition();
  var shouldScroll = behavior.call(
    router,
    to,
    from,
    isPop ? position : null
  );

  if (!shouldScroll) {
    return
  }

  if (typeof shouldScroll.then === 'function') {
    shouldScroll
      .then(function (shouldScroll) {
        scrollToPosition((shouldScroll), position);
      })
      .catch(function (err) {
        if (process.env.NODE_ENV !== 'production') {
          assert(false, err.toString());
        }
      });
  } else {
    scrollToPosition(shouldScroll, position);
  }
});

behavior.callの部分で定義したscrollBehaviorを呼び出しています。その後のIF文でshouldScrollオブジェクトは、asyncを付けているためPromiseと解釈されてしまいエラーが発生しているようでした。

解決策としては、asyncを使用するのをやめるか、Promiseオブジェクトを返却(試してはない)してあげればOKです。

参考文献

Comments