【 jQuery 】ページのスクロール途中で指定要素の位置を「固定 / 解除」する方法

当ブログの今年の目標は、ずばり「UIの強化」としています。そのためにもJavaScriptやjQueryの習得は必須条件となりますから、このブログを通して、いろいろ試していければよいなと思っています。今回は、タイトルの通り、「スクロールの途中で任意の要素の位置を固定/解除する機能」を試すことにしました。指定した要素が画面上のある位置までスクロールされると動きが止まるといった機能です。実装方法を紹介します。

「#top-bar」scrollTop : 0px

まず最初に今回の投稿の記事部分の上と下に「#top-bar」、「#bottom-bar」という2つのバーが設置されているのがわかると思います。

各バーの説明
「#top-bar」と「#bottom-bar」の2つのバー

「#top-bar」は、「ヘッダーバー」の下まで画面がスクロールされると、その場所で位置が固定され、動かなくなります。

「#bottom-bar」は、逆に最初の表示では「フッターバー」の上に固定されていますが、「関連記事」の見出しの位置まで画面がスクロールされると、固定されていたのが解除され画面と一緒に動くようになります。

この機能は今後リッチなユーザーインターフェースを実装する上でかかせない機能になるのではないかと勝手に思っています。実装方法については以下の通りとなります。

.scrollTop()メソッド

この機能の実装には、jQueryの.scrollTop()メソッドを使用しています。この.scrollTop()を使用すると、画面のスクロールの上側の位置情報を取得することができます。つまり「画面を上からどれだけスクロールしたのか」わかります。

.scrollTop()メソッドで取得したスクロール位置の範囲によって、指定要素を固定するのか、それとも固定していたのを解除するのか決めていきます。

スクロール位置の範囲の取得

ただこのスクロール位置の範囲を把握するのが、かなりややこしくて頭が混乱します。考え方としては、「基準となる値」を見つけて、その基準点の上をスクロールするか、下をスクロールするかで、「固定 / 解除」の処理を行うようにさせます。

今回は基準となる値が見つかりやすいように、jQueryで以下の一文を追加し、「#top-bar」上にスクロールトップの位置の値が表示されるようにしました。

$("#top-bar").text("「#top-bar」scrollTop: " + $(this).scrollTop() + "px");

「#top-bar」を固定/解除するためのスクロール位置の範囲

「#top-bar」は、画面の一番上からヘッダーバーの手前までスクロールされると固定されます。つまり、「基準となる値」は、「#top-bar」がヘッダーバーの手前までスクロールされたときのスクロールトップの位置となります。

「基準となる値」を出すには、まず「#top-bar」の最初に表示されている位置の上側の位置情報を取得し、そこから「ヘッダーバーの高さ」を引くことにします。これが「基準となる値」となります。

これをjQueryで書くと、以下のようになります。

$("#top-bar").offset().top - $("#header_bar").height();

上記で取得した値よりも、スクロールトップの位置の値が大きくなれば「#top-bar」は固定され、小さくなれば固定が解除されるようにします。


「#bottom-bar」を固定/解除するためのスクロール位置の範囲

「#bottom-bar」は、画面表示時は、「フッターバー」の真上に固定しています。この固定された状態は、画面を下へスクロールし、「関連記事の見出し」とぶつかるところで解除されます。つまり「基準となる値」は、「関連記事の見出し」がフッターバーの位置までスクロールされたときのスクロールトップの位置となります。

「基準となる値」を出すには、まず「関連記事の見出し」の上側の位置情報を取得しますが、「関連記事の見出し」が画面の下からちょうど現れる時のスクロールトップの位置を取得したいので、そこから「画面(ウィンドウ)の高さ」の分だけ引くことにします。最後に「フッターバー」の高さまで上に上げるために、フッターバーの高さ「24px」を足します

これをjQueryで書くと、以下のようになります。

$("#related-posts").offset().top - $(window).height() + 24;

上記で取得した値よりも、スクロールトップの位置の値が大きくなれば「#bottom-bar」の固定は解除され、小さくなれば固定されるようにします。

指定要素の固定/解除

指定要素を固定/解除するために、「#top-bar」では、jQueryの「.CSS()メソッド」を使ってCSSの「positionプロパティ」の設定(fixed ⇔ static)を直接行っています。

「#top-bar」の固定

$("#top-bar").css({"position": "fixed", "top": "36px"});

「#top-bar」の固定解除

$("#top-bar").css("position", "static");

「#bottom-bar」では、画面表示時に固定させておくために、クラス属性を与えて、そのクラス属性に対しCSSで固定(position:fixed)の設定を行っています。そしてjQueryの「.addClass()メソッド」「.removeClass()メソッド」を使って、「#bottom-bar」に対し、そのクラス属性の付け外しを行っています。

「#bottom-bar」の固定

$("#bottom-bar").addClass("fixed-bottom");

「#bottom-bar」の固定解除

$("#bottom-bar").removeClass("fixed-bottom");

CSS

<style>
.fixed-bottom {
  position: fixed;
  bottom: 24px;
}
</style>

注意点

注意点として、指定要素を固定するために使っている「position: fixed」はIE6には対応していないので、割り切るか、別の対応を考える必要があります。

また、「position: fixed」を指定すると、指定した要素の高さが保持されなくなり、その分画面のスライドが発生します。対策として、「#top-bar」の固定/解除の際に、記事部分のブロック要素(#entry)を「#top-bar」の高さ分だけ位置の上げ下げを行います。

「#bottom-bar」の固定時

$("#entry").css({"position": "relative", "top": $("#top-bar").height() + "px"});

「#bottom-bar」の固定解除時

$("#entry").css({"position": "relative", "top": 0});

さらに、当記事においては「#bottom-bar」の固定/解除についても同時に設定しているので、記事部分のブロック要素(#entry)の位置に合わせて「#bottom-bar」を固定/解除するためのスクロール位置を変更しておく必要があります。

「#bottom-bar」を固定/解除するためのスクロール位置の範囲(「#top-bar」の高さ分を追加)

$("#related-posts").offset().top - $(window).height() + 24 + $("#top-bar").height();

まとめると

以上をまとめると、ソースは以下のようになります。

JavaScript / jQuery

<script>
// ページの読み込みが完全に完了したら以下の処理を実行
window.onload = function () {

  // 「#top-bar」を固定/解除するための基準となる値を取得し、変数「topbar」に代入
  var topbar = $("#top-bar").offset().top - $("#header_bar").height();

  // 「#bottom-bar」を固定/解除するための基準となる値を取得し、
  // 変数「bottombar」に代入
  var bottombar = $("#related-posts").offset().top - $(window).height() + 24
    + $("#top-bar").height();

  // 画面がスクロールされたら以下の処理を実行
  $(window).scroll(function () {

    // 「#top-bar」上にScrollTopの位置の値を表示
    $("#top-bar").text(
      "「#top-bar」scrollTop: " + $(this).scrollTop()
    );

    // ScrollTopの位置が「topbar」よりも値が大きければ、「#top-bar」を固定し、
    // 記事部分のブロック要素の位置を「#top-bar」の高さ分だけ下げる
    if ($(window).scrollTop() > topbar) {

      $("#top-bar").css({"position": "fixed", "top": "38px"});
      $("#entry").css({"position": "relative", "top": $("#top-bar").height() + "px"});

    // 小さければ、「#top-bar」の固定を解除し、
    // 記事部分のブロック要素の位置を元に戻す
    } else {

      $("#top-bar").css("position", "static");
      $("#entry").css({"position": "relative", "top": 0});

    }

    // ScrollTopの位置が「bottombar」よりも値が小さければ、
    // 「#bottom-bar」を固定
    if ($(window).scrollTop() < bottombar) {
      $("#bottom-bar").addClass("fixed-bottom");
    // 大きければ、「#bottom-bar」の固定を解除
    } else {
      $("#bottom-bar").removeClass("fixed-bottom");
    }

  });

}
</script>

CSS

<style>
.fixed-bottom {
  position: fixed;
  bottom: 24px;
}
</style>

HTML

<div id="entry">

<!-- #top-bar -->
<div id="top-bar">「#top-bar」scrollTop : 0</div>

<!--
記事本文
-->

<!-- #bottom-bar -->
<div id="bottom-bar" class="fixed-bottom">「#bottom-bar」</div>

<section id="related-posts">
<!-- 関連記事の見出し -->
<h1 id="#related-posts-title">関連記事</h1>
</section>

</div>

この機能の当ブログでの使い所としては、「サイドバー」を考えています。記事部分の長さと比べると、サイドバーはどうしても短くなるので、記事を読み進めていくと、そのうちサイドバー部分には何も表示されなくなります。すごく無意味なスペースが表示され続けることになり、この状態のままだともったいないので、サイドバーをある位置になったら固定させるようにすることで、サイドバーの有効活用ができるのではないかと考えています。

追記

サイドバーを固定/解除させる方法について新しく記事を書きました。併せて参考にしてみてください。

Web制作の現場で使うjQueryデザイン入門[改訂新版](ドーナッツ本)
  • 『Web制作の現場で使うjQueryデザイン入門[改訂新版]』(通称: ドーナツ本)
  • 著者: 西畑一馬
  • 出版社: KADOKAWA/アスキー・メディアワークス
  • 単行本: 312ページ
  • 発売日: 2013年3月7日
  • ISBN: 4048913913
「#bottom-bar」

コメント一覧

  1. ピンバック: HTML5、JavaScriptが熱かった!2012年を振り返る(ブログ編) | mae's blog

     

  2. はじめまして。
    やりたい事が書いてあると思い、自己解決しようと頑張ってチャレンジしていますが、どうも思い通りにならないのでコメント欄からヘルプミーです。

    まず、誤字発見
    「「#top-bar」の固定」「「#top-bar」の固定解除」のトコ。
    両方の記述が一緒になってます!

    さて、私がやりたいのは、記事のまとめとして書いてあります「サイドバーをある位置になったら固定させるようにすること」です。

    ある位置になったら固定することは出来たのですが、解除が出来ない。。。
    正確にいうと、解除は出来るのですが、ボジションが合わない。。。

    このブログのサイドメニューのように、
    ・ある程度の位置になったら固定
    ・フッター直前で解除

    これを実装したい場合、少しコツがいるような気がしますが、自力解決出来ませんでした。

    つまずいている点は、固定する段階で、
    .css({“position”:”fixed”,”bottom”:”0″,”margin-left”:”50px”});
    ※”margin-left”:”50px”はfixedによるずれの補正

    このように、bottomに合わせて固定しているため、
    普通に解除すると上に上がってしまいます。

    まさに、このブログのように、フッター部分までスクロールダウンした際、スムーズにサイドの固定を解除しつつ、スクロールに合わせて上に動いていいく状況を作るには、どのような条件分岐が必要でしょうか??

    お手すきに際にでも、ご教授お願いしますm(__)m

    福岡  返信

    • > 福岡さん

      コメントありがとうございます。

      > まず、誤字発見
      > 「「#top-bar」の固定」「「#top-bar」の固定解除」のトコ。
      > 両方の記述が一緒になってます!

      こちら直しました。ご指摘ありがとうございます。

      > まさに、このブログのように、フッター部分までスクロールダウンした際、スムーズにサイドの固定を解除しつつ、スクロールに合わせて上に動いていいく状況を作るには、どのような条件分岐が必要でしょうか??

      サイドバーの固定を解除すると、上に上がってしまうということですが、
      解除した時にサイドバーが元の位置に戻ってしまうからだと思いますよ。

      サイドバーの固定を解除すると同時に、今度は親要素の下部に固定させる必要があります。
      .css({“position: absolute”, “bottom: 0”})
      と指定してあげると良いと思いますよ。
      親要素にはposition: relativeを指定してあげてください。

      サイドバーを固定/解除させる方法については、また別エントリーでまとめようと思っています。

      Takanori Maeda  返信

  3. ピンバック: スクロール位置によってサイドバーを固定/解除させる jQueryの実装方法 | mae's blog

     

  4. お世話になります。
    追従ヘッダーに関して調べておりまして、貴サイトに行き着きました。

    参考にさせていただき、非常に助かりました。
    ありがとうございます。

    気づいた点と改善した方法を記載させていただきます。

    top-barがfixedされた瞬間にその直下にある文言等がtop-barの分だけ上に詰まってしまう事象がありましたので、ちょっと不自然かなと思い、修正をいたしました。

    私がやった方法は、top-barの上にあるタグ等にfixedするのと同じタイミングでpadding-bottomをtop-barと同じピクセル分入れてあげましたら、fixedされても上に詰まってしまう事象が改善できましたので、ご報告させていただきます。

    fixedを解除する時にまたpading-bottomを直してあげれば、スクロールに違和感が生じないかと思います。

    私もそこまで詳しくはないので、もっと綺麗に実現する方法などあるかと思いますが、参考程度に見てもらえればと思います。

    このたびはありがとうございました。

    坂上紀彦  返信

    • 坂上さん

      コメントありがとうございます。

      それからご指摘ありがとうございます。
      確かにfixedの際に高さを維持しないことからくる表示の不自然な挙動は気にすると気持ち悪いですね。
      さっそく記事の方にも反映させていただきました。

      私が行った方法としては、position: relativeでtop-barの高さ分だけ位置を変更するようにしてみました。
      おそらくこれで違和感はなくなったと思います。
      助かりました!ありがとうございました!

      Takanori Maeda  返信

  5. ピンバック: jQuery スクロール関連のスクリプト | WEB KAY

     

  6. ピンバック: マップ(地図)と画像の表示入れ替えについて 現在、下のソースの様にし

     

  7. ピンバック: jQuery スクロール関連のスクリプト | WEB KAY NOTEBOOK

     

  8. ピンバック: jQuery基礎講座:スクロール量に応じて要素(ヘッダーなど)を固定する(ZIP付) | 株式会社パナレア

     

  • 必須

コメント