【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) {
    const target = jQuery(this.hash === "" ? "html" : this.hash);
    if (target.length) {
      e.preventDefault();
      const headerHeight = jQuery("header").outerHeight();
      const position = target.offset().top - headerHeight - 20;
      jQuery("html, body").animate({ scrollTop: position }, 500, "swing");

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

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

    jQuery(window).on("load", function () {
      const headerHeight = jQuery("header").outerHeight();
      const 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で使うときの$に注意

WordPressでは$は他のJavaScriptライブラリとの競合の可能性があるため、そのままでは動きません。使用する場合は、jQuery(function($){...});のように引数として$を渡すか、$を全てjQueryに書き直します。

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

// DOMを読み込んで処理
jQuery(function () {
  // #を含むa要素をクリックした時
  jQuery('a[href*="#"]').click(function (e) {
    // スクロール先を指定(href="#"ならばhtml、そうでなければアンカーを代入)
    const target = jQuery(this.hash === "" ? "html" : this.hash);
    // スクロール先の要素が存在する時
    if (target.length) {
      // デフォルトの動作をキャンセル
      e.preventDefault();
      // ヘッダーの高さを取得
      const headerHeight = jQuery("header").outerHeight();
      // スクロール先の位置を計算(ヘッダーと任意の高さを引く)
      const 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つ目、this.hash === ""を採用した点です。

this.hashについて

href="#"の時、this.hashの値は""空文字になります。

href="#section-1"の時、this.hashの値は"#section-1"となります。

つまり、this.hashの値は常に#の後に何かが続くか、空であるかのどちらかです。

// 誤り(=== "#")は存在しないため不要
jQuery(this.hash === "#" || "" ? 'html' : this.hash);

// 正しい(href="#")の時
jQuery(this.hash === "" ? 'html' : this.hash);

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

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

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

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

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

このコードは、ページ内リンクがクリックされるたびにURLを更新するためのものです。

ただし、ページトップボタンの場合は、https://test.com/#という微妙な見た目になってしまうため、ページトップへのスクロールは除外しています。

HISA

その#いらないよね。

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

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

    // ページを読み込んで処理
    jQuery(window).on("load", function () {
      // ヘッダーの高さを取得
      const headerHeight = jQuery("header").outerHeight();
      // スクロール先の位置を計算(ヘッダーと任意の高さを引く)
      const 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だけは直接アンカーに飛んでしまうため、ページトップへジャンプさせるためのコードも併用して対処しています。

詳しい内容

ハッシュを残すかどうか

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

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

共通項目の解説

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

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

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

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

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

ヘッダーを考慮しなくて良い場合は、positionから取り除いてください。

const position = target.offset().top;

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

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

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

例えば、別ページ遷移後にスクロールが不要な場合は、秒数を0にします。

イージングは以下の2つ。

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

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

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

Uncaught Error: Syntax error, unrecognized expression: ##

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

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

確認したい方は、以下のコードを記述してhref="##"をクリックしてみてください。

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

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

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

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

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

おすすめWEBスクール

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

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

お気軽にコメントどうぞ

コメントする

目次