【jQuery】別ページ・固定ヘッダー対応!スムーススクロールの実装方法

【jQuery】全てのページのリンクに対応したスムーススクロール
追記:2023年12月26日

以前掲載のコードはブラウザやサーバー環境によって、別ページ遷移後のスクロールがうまく動作しない場合がありました。アプローチを変え細かく調整したところ、主要ブラウザ全てで正常に動作するようになりました。

スムーススクロールを実装する際、以下のような課題に直面している人も多いのではないでしょうか。

  • 下層ページでは効かない
  • 固定ヘッダーの重なりが解消できない
  • ページ遷移後のスクロールが出来ない

当記事では、これらをまとめて対策した“ほぼ”全部入りのスムーススクロールコードをご紹介したいと思います。

jQueryを使用しているので、初学者の方も取り入れやすいかと思います。

JavaScriptの完全版をまとめました。

目次

検索でよく出てくるあのコード

jQuery('a[href^="#"]').click(function() {
  let header = jQuery("header").innerHeight();
  let speed = 300;
  let href = jQuery(this).attr("href");
  let target = jQuery(href == "#" || href == "" ? "html" : href);
  let position = jQuery(target).offset().top - header;
  jQuery("html, body").animate(
    {
      scrollTop: position
    },
    speed
  );
  return false;
});

スムーススクロールで検索した時、おおよそ同じようなコードが出てくるかと思います。

このコードの問題点

href^="#"は、#から始まるリンク要素しか当てはまらないため、#が文字の後ろにある場合は使えません。

  • href=”/about#section1″
  • href=”example.com/about#section1″
  • href=”#section1″

つまりトップページでしか使えないということです。

ほぼ全部入り!スムーススクロールのコード

// ページ内のスムーススクロール
jQuery(function () {
  jQuery('a[href*="#"]').click(function (e) {
    var target = jQuery(this.hash === "" ? "html" : this.hash);
    if (target.length) {
      e.preventDefault();
      var headerHeight = jQuery("header").outerHeight();
      var position = target.offset().top - headerHeight - 20;
      jQuery("html, body").animate({ scrollTop: position }, 500, "swing");

      if (!target.is("html")) {
        // URLにハッシュを含める
        history.pushState(null, '', this.hash);
      }
    }
  });
});

// 別ページ遷移後のスムーススクロール
var urlHash = location.hash;
if (urlHash) {
  var target = jQuery(urlHash);
  if (target.length) {
    // ページトップから開始(ブラウザ差異を考慮して併用)
    history.replaceState(null, '', window.location.pathname);
    jQuery("html,body").stop().scrollTop(0);

    jQuery(window).on("load", function () {
      var headerHeight = jQuery("header").outerHeight();
      var position = target.offset().top - headerHeight - 20;
      jQuery("html, body").animate({ scrollTop: position }, 500, "swing");

      // ハッシュを再設定
      history.replaceState(null, '', window.location.pathname + urlHash);
    });
  }
}

全体を見て分かる通り、今回のコードは「ページ内」と「ページ外」で完全に切り分けて記述しています。

切り分けることで、実行する順序が読みやすくなり、ページ内リンクのコードだけ使いたいという場合も上部をコピペするだけで済むというメリットがあります。

デメリットとしては、編集する場合は2箇所編集しないといけないといった点がありますが、コード自体短いので大きな問題ではないと判断しています。

注意

おまじないでお馴染みのjQuery(function (){...})などで、二重に囲まないようにしてください。loadイベントが入れ子になるとうまく動作しなくなります。

$についての補足

通常、WordPress環境では$は他のJavaScriptライブラリとの競合の可能性があるため、jQueryとして記述しないと動きません。そのため、$は使用していませんが、もし使用する場合は、jQuery(function($){...});のように、引数として$を渡して関数を実行することで、そのブロック内では$を jQuery オブジェクトとして利用できます。

ページ内のスムーススクロールの解説

// DOMを読み込んで処理
jQuery(function () {
  // #を含むa要素をクリックした時
  jQuery('a[href*="#"]').click(function (e) {
    // スクロール先を指定(href="#"ならばhtml、そうでなければアンカーを代入)
    var target = jQuery(this.hash === "" ? "html" : this.hash);
    // スクロール先の要素が存在する時
    if (target.length) {
      // デフォルトの動作をキャンセル
      e.preventDefault();
      // ヘッダーの高さを取得
      var headerHeight = jQuery("header").outerHeight();
      // スクロール先の位置を計算(ヘッダーと任意の高さを引く)
      var position = target.offset().top - headerHeight - 20;
      // スクロール実行(500ミリ秒、swingを指定)
      jQuery("html, body").animate({ scrollTop: position }, 500, "swing");

      // ページトップ以外の時
      if (!target.is("html")) {
        // URLにハッシュを含める
        history.pushState(null, '', this.hash);
      }
    }
  });
});

よく出回っているコードとの違いは、以下の2つ。

a[href^="#"]
↓
a[href*="#"]

jQuery(href == "#" || href == "" ? "html" : href);
↓
jQuery(this.hash === "" ? 'html' : this.hash);

1つ目、href*="#"では#を含む場合となるので、href属性に#が含まれていればクリックイベントが実行されます。

2つ目、href === "#"を採用しなかった点です。
※若干コードが異なりますが、三項演算子でのアプローチは一緒です。

href===”#”ってなに?

<a href="#">の場合、this.hash === "#" ではなくthis.hash === ""が成立します。

ではthis.hash === "#" が成立するのはどんな時か。

試しに以下のコードで実験してみました。

var link = document.querySelector('a');
console.log(link.hash === "#");

リンクを踏んだ時、もし、式が成り立てばtrue、成り立たなければfalseがコンソールに表示されます。

<!-- false -->
<a href="">

<!-- false -->
<a href="#">

<!-- false -->
<a href="##">

全てfalseとなり、どのリンクも当てはまらないというまさかの結果になりました…。

以下はAIによる回答です。

申し訳ありませんが、通常のシナリオでは <a href="#"> のようなリンクにおいて console.log(link.hash === "#"); が成り立つようなものは存在しません。なぜなら、<a href="#"> は通常、同じページ内のトップにジャンプするリンクであり、その hash プロパティは空文字列 ("") に設定されるからです。

もし console.log(link.hash === "#"); が成り立つ状況がある場合、それは通常の挙動から外れたケースである可能性があります。他のJavaScriptコードやCSSスタイルなどが影響を与えているかもしれません。

chat.openai.com

要約すると、<a href="#">の場合はthis.hash === ""で、href === "#"と一致するものは存在しないということ。

そもそも#を含むリンクをクリックしたときに起こるイベントなので、「href=""の時は”html”」という指定も矛盾しています。

このコードが使われるようになった背景が分かる方いれば教えてください。

ページトップボタンのhrefを変更する場合

もしページトップボタンをhref="#top"とする場合は、以下のように指定します。

var target = jQuery(this.hash === "#top" ? 'html' : this.hash);

URLにハッシュを含めて更新

// ページトップ以外の時
if (!target.is("html")) {
  // URLにハッシュを含める
  history.pushState(null, '', this.hash);
}

ハッシュの更新は、ページトップへのスクロールを除外しています。

ページトップボタンが押されるとURLが、https://test.com/#という微妙な感じになってしまうのを防ぐためです。

HISA

その#いらないよね。

そもそもURLにハッシュをつけたくない場合は、このコードを削除してください。

別ページ遷移後のスムーススクロールの解説

// ハッシュを取得
var urlHash = location.hash;
// ハッシュが存在する時
if (urlHash) {
  // スクロール先を取得
  var target = jQuery(urlHash);
  // スクロール先が存在する時
  if (target.length) {
    // ページトップから開始(ブラウザ差異を考慮して併用)
    history.replaceState(null, '', window.location.pathname); // ハッシュを削除
    jQuery("html,body").stop().scrollTop(0); // ページトップへジャンプ

    // ページを読み込んで処理
    jQuery(window).on("load", function () {
      // ヘッダーの高さを取得
      var headerHeight = jQuery("header").outerHeight();
      // スクロール先の位置を計算(ヘッダーと任意の高さを引く)
      var position = target.offset().top - headerHeight - 20;
      // スクロール実行(500ミリ秒、swing指定)
      jQuery("html, body").animate({ scrollTop: position }, 500, "swing");

      // ハッシュを再設定
      history.replaceState(null, '', window.location.pathname + urlHash);
    });
  }
}

スクロール開始位置の指定

// ページトップから開始(ブラウザ差異を考慮して併用)
history.replaceState(null, '', window.location.pathname); // ハッシュを削除
jQuery("html,body").stop().scrollTop(0); // ページトップへジャンプ

スクロールをページトップから開始させるための処理です。

不要であれば、こちらのコードは削除していただいて結構です。

ブラウザがアンカーリンクにジャンプする前にハッシュを取り除けば、ページトップ(初期位置)が表示されるだろうと思い試してみたらうまくいきました。

ただ、Firefoxだけは直接アンカーに飛んでしまうため、ページトップへジャンプさせるためのコードも併用して対処しています。

詳しい内容

スクロールさせるタイミングを変更する

正しい位置の取得やローディングアニメーションなどを考慮し、ウェブページの全てのリソース(画像、スタイルシート、スクリプトなど)が読み込まれた後にスクロールを実行しています。

スクロールをより早いタイミングで開始したい場合、以下のようにコードを変更できます。

// ページを読み込んで処理(やや遅い)
jQuery(window).on("load", function () {
  // スクロール実行
});

↓

// 非同期処理(早い)
setTimeout(function () {
  // スクロール実行
}, 0);

遅延時間には 0 ミリ秒を指定していますが、必要に応じて任意の秒数を指定することもできます。

ハッシュを残すかどうか

// ハッシュを再設定
history.replaceState(null, '', window.location.pathname + urlHash);

ハッシュを取り除いたためスクロール後に再設定していますが、不要であればこちらは削除してもOKです。

共通項目の解説

スクロール位置の調整方法

// ヘッダーの高さを取得
var headerHeight = jQuery("header").outerHeight();

ヘッダーの指定は、必要であればjQuery(".header")jQuery("#header")など特定のクラスや IDを持つものに変更してください。

// スクロール先の位置を計算(ヘッダーと任意の高さを引く)
var position = target.offset().top - headerHeight - 20;

ヘッダーが被る問題の対策として、アンカーの位置からヘッダーの高さを引き、余白を設けたい場合は任意の数値(px単位)をさらに引くことで調整可能です。

ヘッダーを考慮しなくて良い場合は、positionから不要な要素を削除して元のアンカー位置に戻します。

var position = target.offset().top;

スクロールの時間と動きの調整

// スクロール実行(500ミリ秒、swingを指定)
jQuery("html, body").animate({ scrollTop: position }, 500, "swing");

スクロールの実行時間と、イージング(動き方)はここで指定できます。

イージングは以下の2つが選べます。

  • linear:一定の速度でアニメーションが進行する
  • swing:始めは遅く、途中は速く、最後は遅くアニメーションが進行する

余談:どうでも良いエラー

検証中に分かったのですが、アンカーリンクがhref="##"ように#が連続している場合jQuery(this.hash)によって生成されるセレクタが正しくないため、jQueryがエラーを返してきます。

Uncaught Error: Syntax error, unrecognized expression: ##

「未捕捉のエラー: 構文エラー、認識できない表現: ##」

このような使い方はテスト環境でしか行いませんが、正しい表記で書きましょうということですね。

確認したい方は以下のコードをどうぞ。

jQuery('a[href*="#"]').click(function () {
  var target = jQuery(this.hash);
});

まとめ:全てのページにスムーススクロールを

以上、jQueryでスムーススクロールを実装する方法を解説しました。

出来るだけ安定するようにリファクタリングしましたが、もし動作が不安定な場合にはご連絡いただければ幸いです。

ご参考くださりありがとうございました。

おすすめWEBスクール

WEB制作やWEBデザインを学びたいなら、SNSでも話題の「デイトラ」がおすすめ!
どのコースも10万円前後と業界最安値で、副業や転職に向けて十分なスキルを身につけることができます。

\ ここから飛べます! /

役に立ったら他の方にシェア

お気軽にコメントどうぞ

コメントする

目次