ハッシュだって
超えられる
ハッシュのままでページを超えて
スムーススクロール

image: ハッシュだって超えられる
Toplabo
Let the Hash Across Pages.

この記事は『ページをまたいで スムーススクロール』の続きになります。先にそちらをご覧ください。

ハッシュでは不可能なページまたぎスムーススクロールを実装する

 | 2026/02/28

基本

なぜ、ハッシュではスムーススクロールが不可能か

  • ことの発端

    前回の概要では、”同じページ内ではアンカーにハッシュを記述してもスムーススクロールできるが、別ページからの遷移ではハッシュ付きのURLは、いきなり該当コンテンツが表示されスムーススクロールされない”と解説しました。
    それでも、無理やりリセットしてスムーススクロールする方法はあるが、意味ないので解説しないとしました。
    が、よくよく考えるとヘッダのリンク記述をハッシュで統一できるのは小さいけどメリットでもあるかもしれないし、ひとつ解説するかとなりました。
    まあ、前回の手法で統一してしまえばそういう問題もないですし、もちろん私には無用ですが爆。

    まずはハッシュ付きのURLをブラウザが処理する流れの解説
    ページを跨ぐときのブラウザの処理の流れは下記になります。

    1.ページ遷移
    2.ページ読み込み開始
    3.ブラウザが hash を検出
    4.即スクロール
    5.JS実行

    つまりJSが制御する前にブラウザがハッシュのリクエストを終了させているわけです。JS実行前にすでに評価されてるわけです。
    そもそもハッシュによるスクロールはHTML Standard の “navigate to a fragment” アルゴリズムに含まれるブラウザの標準処理です。
    歴史的にもCSSやJSが導入される前から存在していた仕様でもあり、ブラウザは最優先で処理します。JSよりも早く、CSSよりも優先されるのです。

方法

ポイントはいかにして、HTMLより先にJSを実行し、なおかつHTMLが構築されたのち再度実行させるか

  • ブラウザのスクロールをストップさせることと、リクエストをいかに最速で取得し、読み込み後に再度取得するかの2点が重要

    ”ハッシュでもページ跨ぎのスマートスクロール”ですが、検索するといろいろヒットします。CSSだけで可能としている記事は問題外として、他はブラウザのスクロールをキャンセルするため、history.replaceStateを使ってURLからハッシュを削除し、window.scrollTo(0, 0);でスクロール値をゼロにリセットする手法がほとんどのようです。
    で、このhistory.replaceStateメソッドですが、これはURLの履歴を処理するメソッドであってブラウザの挙動を制御するためのメソッドではない。
    replaceState: 「履歴の書き換え」であり、表示上のURLは変わりますが、ブラウザがすでに開始している「ロード完了時のジャンプ予約」をキャンセルする仕様にはなっていません。
    ただしクロムなど、環境によってはちょっとちらつく程度で、スクロールすることもありますが、これはよく併記してあるwindow.scrollTo(0, 0);の強制リセットのおかげでhistory.replaceStateのおかげではない。
    少なくともfirefoxでは最初に該当コンテンツが表示され、その後TOPに戻り、さらにまたスムーススクロールで再び該当コンテンツが表示されます。
    クロムなども、目に見えないだけで、一瞬、該当コンテンツは表示されていて、その後強制リセット、そしてスクロールしています。なんとも無駄の多い洗練されてない(スムースでない)スムーススクロールです。

  • エレガントにスムーススクロールさせる

    次の方法でfirefox含めてスムーススクロールが可能です。

  • 1. history.scrollRestoration を manual にする

    これが最も強力な方法です。ブラウザの「スクロール位置を復元しようとする挙動」を無効化します。これをhead要素内のスクリプトで実行すれば、ハッシュによる自動ジャンプを阻止できます。
    head要素の最上部へ記述します。

    <head>
    	<script>
    		// ブラウザの自動スクロール復元・ジャンプ機能を「手動」に設定
    		if ('scrollRestoration' in history) {
    			history.scrollRestoration = 'manual';
    		}
    
    		// 必要であればハッシュも消しておく
    		history.replaceState(null, '', window.location.pathname + window.location.search);
    	</script>
    </head>
  • 古いブラウザ対応

    scrollRestoration が効かない古いブラウザや特定の状況への対策として、DOMが構築されるタイミングで強制的にトップへ戻す処理を入れます。
    window.scrollTo はIEなどでも対応していたメソッドなのでほとんどのブラウザに効果があると思われます。

    window.addEventListener('load', () => {
    	window.scrollTo(0, 0);
    
    	// 必要に応じて元に戻す(次回の操作のため)
    	setTimeout(() => {
    		history.scrollRestoration = 'auto';
    	}, 100);
    }, { once: true });
  • 3.最後にリクエストされている、ハッシュ付きのリンクの処理です。

    まず、history.scrollRestoration処理をする前に、URLからハッシュを抜き出します。

    	//リクエストにハッシュがあればストレージへ一時保存
    	const request = new URL(location.href).hash;
    	if(request) sessionStorage.setItem("hash", request);
    
  • ブラウザのスクロール処理をストップさせた後はいったんJSは動作を停止します。
    DOMの構築が完了した時点で再度JSでストレージへ保存したリクエストを読み込みます。
    それをスムーススクロールさせます。

    document.onreadystatechange = function () {
    	if (document.readyState === 'complete'){
    		console.log('complete');
    		const hash = sessionStorage.getItem("hash");
    		if (!hash) return;
    		const target = document.querySelector(hash);
    		requestAnimationFrame(() => {
    			target.scrollIntoView({ behavior: 'smooth' });
    		});
    
    		setTimeout(() => {
    		history.scrollRestoration = 'auto';
    		}, 100);
    	}
    }

まとめ

ハッシュによるブラウザのスクロールはストップできないだろうと思っていたが

  • ほんとに今のWEBは進化している

    ブラウザのスクロール処理をストップさせるのはぜったい無理だろとおもっていたんだけど、実はあったというのが今案件の驚きというか非常に学びの多いものでした。
    また、firefoxとGoogle Chrome でもこんなに動作が違うとも思っていなかった。
    特に最近の新しいCSSプロパティにはfirefoxが足かせで実装できないのが多いとも思う。
    しかし、firefoxとchromeはいわば衆議院と参議院みたいなもんで(違うか)それぞれ重要な位置づけなのはたしかだよね。
    さて、今回の解説したコードはシンプルに記述するため、ハッシュリンクのページ跨ぎのスムーススクロールだけに特化しています。
    なのでページ内スムーススクロールも欲しい場合は最後に前回の記事などを参考に別途追加してください。

『ハッシュだって超えられる』関連のお薦め

このサイトで紹介しているコード、プログラムなどは個人の学習目的で作成されたものであり、いかなる保証も行いません。
利用はすべて自己責任でお願いします。
ただし、このページで紹介しているプログラムやビジュアルなどはご依頼いただければ実装を賜ります。
お問い合わせはこちら