スクロール位置によってサイドバーやナビゲーションを固定・解除する jQuery のシンプルな実装方法と注意点

このエントリーをはてなブックマークに追加

ページをスクロールしていると、途中から要素が固定表示される動きをするサイトを時折見ます。ナビゲーションだったりサイドバーの目立たせたいところだったり、用途は様々。

例えば、SaCSS の 以前のスペシャルページでも利用していたように、ページ途中で出てくるナビゲーションが、画面の一番上に触れると固定されて以降ずっとついてくるような動きをする。

SaCSS WordBench Sapporo,Late2013

この実装はそれほど難しくなく、比較的簡単に実装できるのですが、諸所気にかけておいたほうが良い部分があったりする。

シンプルな実装方法を紹介しつつ、注意点も書いておこうと思う。

シンプルな実装方法

固定したい要素の表示位置を .offset().top で取得し、 $(window).scrollTop() が 要素の位置よりも大きくなった時に、 positionfixed にする。

HTML

<div class="header"></div>
<div class="nav nav--typeA">ナビゲーション</div>
<div class="contents">
  <ul class="zebra">
    <li>位置チェック用テキスト01</li>
    〜 省略 〜
    <li>位置チェック用テキスト100</li>
  </ul>
</div>

CSS(必要部分を抜粋)

.nav {
  width: 100%;
}
body.is-fixed .nav--typeA {
  position: fixed;
  top: 0;
  left: 0;
}

bodyis-fixed がついていたら固定表示になるように

JavaScript(jQuery)

$(function () {
  var $body = $('body'),
      $navTypeA = $('.nav--typeA'),
      navTypeAOffsetTop = $navTypeA.offset().top;
  
  $(window).on('scroll', function () {
    if($(this).scrollTop() > navTypeAOffsetTop) {
      $body.addClass('is-fixed');
    } else {
      $body.removeClass('is-fixed');
    }
  });
});

サンプル1(CodePen)

上記の例は fixed にするために class を追加している。 position:fixed; のスタイルは、あらかじめ CSS に class の追加で切り替えできるようにしておくのが良い。

シンプルな実装方法の注意点

実はこの実装方法だと、ちょっとした注意点が2つある。

1つは fixed に変わるとその分の高さが消えるため、コンテンツ部分が上に押し上げられてしまうという問題だ。

実際に動かしてみるとわかるのだが、fixedになった瞬間に、サンプルのコンテンツのリスト部分がナビゲーションと書かれた要素の下に瞬時に入り込んでしまっているのが確認できる。

このままだと一瞬ガタッと動いたようにみえて、スムーズな切り替わりで見えない。

がたっと動かさないための改善方法

改善の方法は要素が fixed になったタイミングで、固定にする要素の高さ分余白を追加するだけである。

CSS(がたつき改善版)

.nav {
  width: 100%;
}
body.is-fixed .header {
  margin-bottom: 64px;
}
body.is-fixed .nav--typeA {
  position: fixed;
  top: 0;
  left: 0;
}

サンプル2(CodePen)

固定の要素の高さが固定の場合は、このようにCSSのみで対応可能である。もしも高さが変化するような場合は(レスポンシブでテキスト量で変わるような場合など)JavaScriptで高さをチェックして、そのサイズ分の余白を追加するというコードを書くだけである。

もう一つの注意点

がたつきの他に、もう一つの注意点は、 .offset().top で表示位置を取得するタイミングである。具体的に言うと、画像の読み込みを待たずに位置を取得すると、想定していた位置で動作しなくなってしまうという問題が発生する。

先ほどのサンプルのナビゲーションの上に、大きめの画像をいれたものを例にしてみる。

サンプル3(CodePen)

画像がキャッシュされていたりするならば問題はないが、初回読み込み時などは変な位置で JavaScript が動作してしまう。

画像の高さも計算にいれて動作させるためには?

画像の高さがきちんと入ってから動作させるようにすると、固定開始位置が意図した通り要素の top の位置となるのだが、その方法は jQuery の $(document).ready() のタイミングで動かすのではなく、 $(window)load のタイミングで動かせば良いだけである。

$(window).on('load', function () {
  var $body = $('body'),
      $navTypeA = $('.nav--typeA'),
      navTypeAOffsetTop = $navTypeA.offset().top;
  
  $(window).on('scroll', function () {
    if($(this).scrollTop() > navTypeAOffsetTop) {
      $body.addClass('is-fixed');
    } else {
      $body.removeClass('is-fixed');
    }
  });
});

サンプル4(CodePen)

これで画像のキャッシュがあろうがなかろうが、画像の高さが入ってから位置が取得されて動くようになる。

途中から固定表示を入れる場合は、この2つに気をつけて対応することをおすすめします。