Written by TSUYOSHI

jQueryでメニューを固定 スクロールしてもついてくるメニュー 【初心者でも実装できるsticky動作】

JavaScript jQuery PROGRAMMING

本記事では、スクロールしたら上部のヘッダーメニューが画面上部に張り付いて追尾してくるメニューの実装について、実装方法とソースコードの解説をします。

この記事を読むことによって、ページをスクロールした時にメニューがついてくるなど、スクロールで処理を行う機能についてjQueryを使って実装ができるようになります。

この記事を書いている僕はJavaScriptが得意分野でフロントエンドのプログラマー(フリーランス)として仕事をしています。これまで企業などで3年以上のWeb制作経験があり、HTMLやCSSのマークアップからJavaScriptのフレームワークであるVue,React,Nuxtなどフロントエンドの開発を得意としています。

応用すれば様々な実装が可能になるので、使いこなせるようにしましょう。

GitHubのサンプルコード

サンプルコードとして、GitHubにコードを上げています。
https://github.com/it-web-life/jquery_scroll_fixed_header
git clone」するか、緑色の「Code」ボタンをクリックして「Download ZIP」をクリックしてダウンロードして中身を解凍してください。

ディレクトリ構成

  • 「base」ディレクトリ以下:スクロールでメニューがついてくる処理を実装する前のベースとなるコード
  • complete」ディレクトリ以下:スクロールでメニューがついてくる処理を実装したコード

実装前の動作は以下の通りです。
https://it-web-life.github.io/jquery_scroll_fixed_header/base/index.html

スクロールしたら処理をするjQuery処理

一定のところまでスクロールしたら処理をするという処理について解説します。
まずは事前準備として、index.html jQuery本体の読み込みとscript.jsを作成して読み込ませます。

  <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
  <script src="script/script.js"></script>

今回は、navタグがついているメニュー部分をスクロール時に固定するようにしたいと思います。
固定したい要素であるnavタグには、「js-sticky」クラスを付与します。jQueryの処理では「js-sticky」Classが付いている要素を固定するよう処理を書きます。
固定する時は「is-fixed」Classをつけるようにして、CSSで固定するときのスタイルを記述しておきます。

今回は以下の手順でjQueryの処理を行います。

  1. ページのスクロールイベントを検知したら、.js-stickyが付いている要素(メニューの要素)がもとの位置より下にスクロールされているかを確認する。
  2. 画面の上部(scrollTop)と比較して、.js-stickyが付いている要素がもとの位置より
    下のときは「is-fixed」Classを付与して固定する。
    上のときは「is-fixedClassを削除して固定を解除する。

1. ページに固定すべき要素が1つしかない場合のソースコード

ソースコードは以下の通りです。

▼index.html (nav部分)
修正前)「<nav class=”outline”>」
修正後)「<nav class=”js-sticky outline”>」
※ここの要素にスクロール位置によってさらに「is-fixed」Classが追加されることになります。
固定時) <nav class=”js-sticky outline is-fixed”>

▼style.css に以下を追加

.outline.is-fixed {
  position: fixed;
  top: 0;
}

navタグに.outlineがもともと付いているので、そこにさらに「is-fixed」のスタイルを当てています

▼script.js

$(function() {
  /** .js-stickyがページ内に1つしか存在しない前提の場合 */
  // ①固定する要素を取得
  var $fixedElement = $('.js-sticky');

  // ②固定する要素があるとき
  if ($fixedElement.length) {
    // ③固定する要素の高さを取得
    var fixedOffset = $fixedElement.offset().top;

    // ④スクロールイベント
    $(window).scroll(function() {
      // ⑤固定要素の位置よりも下にスクロールされているとき
      if ($(this).scrollTop() > fixedOffset) {
        // ⑥要素を固定する
        $fixedElement.addClass('is-fixed')
        return;
      }

      // ⑦元のメニュー位置より上のときはもとに戻す
      $fixedElement.removeClass('is-fixed');
    });

  }
});

※このコードは「.js-sticky」が1ページに1つしか存在しないときのコードです。後に複数つけても大丈夫なコードに変更します。
コードについて解説します。

  • ①固定する要素を取得
    「.js-sticky」がついている固定すべき要素を取得します。
  • ②「$fixedElement.length」で要素が取得できているか確認します。
    jQueryでは要素が取れなくてもundefinedにはならないため「if (!$fixedElement)」と判定すると常にtrueになってしまうため、length(要素がないときは0になる)で今回は判定しています。
  • ③固定する要素の高さを取得
    ページ読み込み時にページ内での「.js-sticky」要素の位置を「【要素】.offset().top」で取得しています。
  • ④スクロールイベント
    スクロールしたときに処理をしています。後に追加しますが、最終的にはここにthrottleを入れて処理を軽くします。
  • ⑤固定要素の位置よりも下にスクロールされているかを判定
    「$(this).scrollTop()」で現在のスクロールしている画面のトップの位置を取得して、fixedOffset(.js-sticky要素の元の位置)よりも下だとif文の処理をします。
  • ⑥要素を固定する
    「【要素】.addClass(‘is-fixed’)」で、「.is-fixed」を付与します。returnで処理を切ります。
  • ⑦元のメニュー位置より上のときはもとに戻す
    .js-sticky要素の元の位置よりもスクロールしている画面トップの位置が上の時は、「【要素】.removeClass(‘is-fixed’)」で、「.is-fixed」を削除し、固定を解除します。

2. ページに固定すべき要素が複数ある場合を想定したソースコード

先ほどの1.のコードだと、「.js-sticky」のClassがついた要素がページ内に2つ以上あると正常に動作しなくなります。

.js-sticky」が複数あっても問題ないように変更していきます。修正するのはjQueryのコード部分だけですが、固定要素が2つある場合は、さらに追加で固定する箇所のCSS(is-fixedが付いた時のスタイル)を追加する必要があります。とりあえずは、jQueryのみを今回は修正します。

▼script.js

$(function() {
  // 固定する要素を取得
  var $fixedElement = $('.js-sticky');

  // 固定する要素があるとき
  if ($fixedElement.length) {
    // ★①固定する要素の高さを取得(複数ある場合に対応)
    var fixedOffsets = $fixedElement.map(function(index, element) {
      return $(element).offset().top;
    });

    // スクロールイベント
    $(window).scroll(function() {
      // スクロール位置を取得
      var scrollTop = $(this).scrollTop();

      // ★②.js-stickyの要素をすべてチェックする
      fixedOffsets.each(function(index, offset) {
        // ★③固定要素の位置よりも下にスクロールされているとき
        if (scrollTop > offset) {
          // ★④要素を固定する
          $fixedElement.eq(index).addClass('is-fixed')
          return;
        }

        // ★⑤元のメニュー位置より上のときはもとに戻す
        $fixedElement.eq(index).removeClass('is-fixed');
      });
    });

  }
});

今回は変更がある部分のみ、番号を振って解説します。

  • ①固定する要素の高さを取得(複数ある場合に対応)
    取得した「$(‘.js-sticky’)」要素は複数あるため配列であり、mapメソッドを使って順番に処理していきます。
    mapはES6でもありますが、jQueryでは「.map( callback(index, 【配列内の要素】) )」という形で処理して、コールバック関数内でreturnした値で新たな配列を組み直すことができます。
    今回はmapを使って「return $(element).offset().top;」により、「$(‘.js-sticky’)」要素の各オフセット値(ページトップからの位置)を配列で管理しています。「fixedOffsets」に配列で各要素のオフセット値が入ります。
  • ②.js-stickyの要素をすべてチェックする
    「fixedOffsets.each(function(index, offset) {」では、「.each」を使って配列要素をすべてループし処理しています。
    「fixedOffsets」には配列で「.js-sticky」のオフセット値が入っているので、.each内の「offset」でオフセット値が毎回渡されて処理されます。
  • ③固定要素の位置よりも下にスクロールされているとき
    「if (scrollTop > offset) {」にて、ページスクロールの位置と渡されたoffsetを比較しています。
  • ④要素を固定する
    「$fixedElement.eq(index).addClass(‘is-fixed’)」によって、該当する要素に「.is-fixed」Classを付けています。
    「.eq(index)」によって、$fixedElementのindex番目の要素を指定しています。またreturnで処理を切って、後の処理をさせないようにしています。
  • ⑤元のメニュー位置より上のときはもとに戻す
    $fixedElement.eq(index).removeClass(‘is-fixed’);」によって、該当する要素から「.is-fixedClassを削除しています。

3. ページに固定すべき要素が複数ある場合を想定して、throttleも入れたソースコード

2のコードだと、scrollイベントの発生の度に処理をしているため、無駄に処理が重くなっています。そのため、throttle処理をいれてコールバック関数が呼ばれる回数を間引くようにします。
※throttleを詳しく知りたい場合は、以前の記事の「【脱初心者】 throttle / debounceとは? jQueryでresizeやscrollの処理負担を軽減する」を参考にしてみてください。

変更は簡単で、throttleを使えるようにプラグインを読み込みして、$(window).scroll(function() {」の処理部分に「$.throttle(【秒数】, 【コールバック関数】)」を入れ込めばOKです。

▼index.html
<script src=”https://cdnjs.cloudflare.com/ajax/libs/jquery-throttle-debounce/1.1/jquery.ba-throttle-debounce.js”></script>」をjQuery本体を読み込んだ後に読み込ませます。

  <script src="https://code.jquery.com/jquery-3.3.1.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-throttle-debounce/1.1/jquery.ba-throttle-debounce.js"></script>
  <script src="script/script.js"></script>

▼style.css (変更なし)

/* 要素を固定する .is-fixed */
.outline.is-fixed {
  position: fixed;
  top: 0;
}

▼script.js

$(function() {
  // 固定する要素を取得
  var $fixedElement = $('.js-sticky');

  // 固定する要素があるとき
  if ($fixedElement.length) {
    // 固定する要素の高さを取得(複数ある場合を考えて配列で保存)
    var fixedOffsets = $fixedElement.map(function(index, element) {
      return $(element).offset().top;
    });

    // スクロールイベント
    $(window).scroll(
      $.throttle(250, function() {
        // スクロール位置を取得
        var scrollTop = $(this).scrollTop();

        // .js-stickyの要素をすべてチェックする
        fixedOffsets.each(function(index, offset) {
          // 固定要素の位置よりも下にスクロールされているとき
          if (scrollTop > offset) {
            // 要素を固定する
            $fixedElement.eq(index).addClass('is-fixed')
            return;
          }

          // 元のメニュー位置より上のときはもとに戻す
          $fixedElement.eq(index).removeClass('is-fixed');
        });
      })
    );
  }
});

最終的なソースコードは、「complete」ディレクトリの内容になります。
https://github.com/it-web-life/jquery_scroll_fixed_header/tree/master/complete

メニュー固定を実装したサンプルのURL
https://it-web-life.github.io/jquery_scroll_fixed_header/complete/index.html

まとめ

今回は、スクロールした時にメニューがついてくる実装を解説しました。
他にも、右側にメニューがあるときにスクロールで付いてくるといった実装も今回のコードでCSSを追加(is-fixedのスタイル)すれば簡単に対応が可能です。

また、今回のようなページ内リンクがある例では、他の記事でまとめている「スムーススクロール」なども併せて実装すればよりよいUI(ユーザーインターフェース)にすることができます。

以上となります。
ご参考になれば幸いです。

※当サイトでは一部のリンクについてアフィリエイトプログラムを利用して商品を紹介しています