jQueryでスクロールしても固定されるサイドバーを実装する(レスポンシブ対応)

ページをスクロールするとサイドバーが画面上部に固定されるコードを書いてみました。
デモでは、サイドバーの項目が1~40番目までスクロールされたら、サイドバーが固定されるようになっています。
このサイトでもアレンジを加えて利用していますので(2015年3月現在)、動きを確認してみてください。

デモページ

このサイドバーの概要

以下の条件で作成しました。

  • 一定の長さをスクロールすると、サイドバーが画面の上部を基準に固定される
  • ページの最後までスクロールしたら、サイドバーは親要素の下部(フッターの上)に固定される
  • 横幅が特定のサイズ以下になったら、サイドバーが固定されるのを解除する
  • メインエリアの方がサイドバーより短かったら固定しない

html

htmlでキモなのが、固定したいサイドバーの要素を覆う要素が必要ということです。
ここでは#side-wrapになります。これがないとサイドバーをposition:fixedにした時にsafariでは基準値が左上になってしまい、メインエリアに重なってしまいます。

<header id="header">
 <h1>タイトル</h1>
</header>
<div id="wrap" class="clearfix">
 <div id="main-wrap">
  <div id="main">
   <p>メインエリア</p>
  </div>
 </div>
 <!-- #side-wrap がないとsafariで表示が崩れます -->
 <div id="side-wrap">
  <div id="side">
   <ol>
    <li>サイドバーのリスト</li>
    <li>サイドバーのリスト</li>
     <!--中略-->
    <li>サイドバーのリスト</li>
    <li>サイドバーのリスト</li>
   </ol>
  </div>
 </div>
<!--/ #wrap--></div>

CSS

#wrapが#sideの親要素になるよう、position: relative; にします。
#sideは display:inline-block; で回りこむようにします。
34~44行は、jQueryで付け替えるクラス名のプロパティを指定しています。
レスポンシブにも対応していますが、メディアクエリを使用せずにCSSを書けば、固定されたレイアウトも可能です。

/*PC用のCSS*/@media screen and (min-width: 641px) {
h1 {
    padding: 80px 0 0;
 font-size:30px;
}
#header,
#footer {
 height: 200px;
 text-align: center;
 max-width: 1000px;
 margin: 10px auto;
}
#wrap {
 position: relative;
 max-width: 1000px;
 margin: 0 auto;
}
#main-wrap {
 float: left;
 max-width: 700px;
 width: 70%;
}
#main {
 margin: 0 20px 0 0;
 height: 8000px;
}
#side {
 max-width: 300px;
 width: 30%;
 display:inline-block;
 background: #ddd;
}
.fixed-side {
 position: fixed;
 top:0;
}
.bottom-side {
 position: absolute;
 bottom: 0;
}
.static-side {
 position: static;
}
}
/*SP用のCSS*/@media screen and (min-width: 0px) and (max-width: 640px) {
h1 {
 padding: 30px 10px;
 font-size:20px;
}
#main {
 margin: 10px;
 height: 1000px;
}
#side {
 padding: 5px;
 display: block;
 margin: 10px;
 background: #ddd;
}
}

jQuery

サイドバーの固定を解除するタイミングなどは、長さによって変えたほうが良い場合があるので、適宜変数の値を調整してみてください。

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script>
$(function(){
 //各エリアの高さを取得
 var pageH = $('body').height();
 var windowH = $(window).height();
 var mainH = $('#main').outerHeight();
 var sideH = $('#side').outerHeight();
 var headerH = $('#header').outerHeight();
 var footerH = $('#footer').outerHeight();

 //サイドバーの高さ+ヘッダーの高さ
 var viewSide = sideH + headerH ;
 //サイドバーを固定する高さ
 var fixedSide = headerH + sideH - windowH;
 //ページを最後までスクロールした時の高さ
 var scrollBottom = pageH - windowH - footerH ;

 //ウィンドウサイズを変更した時にウィンドウの高さを取得し直す
 $(window).resize(function(){
  windowH = $(this).outerHeight();
  windowW = $(this).outerWidth();
 });

 $(window).scroll(function(){
  //スクロールの値を取得
  var scrollTop = $(this).scrollTop();
  $('.scroll').text('スクロール値:' + $(this).scrollTop());
  /*ウィンドウサイズよりサイドバーの方が長く、
  尚かつサイドバーの最後までスクロールされたら*/  if(windowH < viewSide && scrollTop > fixedSide ) {
   //サイドバーを固定
   $('#side').addClass('fixed-side');
  }else{
   //条件から外れたらサイドバーの位置を初期値にする
   $('#side').removeClass('fixed-side');
  }
  //ページの最後までスクロールされたら
  if( scrollTop > scrollBottom){
   //#wrapの下を基準としてサイドバーを絶対配置
   $('#side').removeClass('fixed-side');
   $('#side').addClass('bottom-side');
  }
  else{
   $('#side').removeClass('bottom-side');
  }
  //メインエリアの方がサイドバーより短かったら
  if( mainH < sideH ){
   //サイドバーの位置を初期値にする
   $('#side').addClass('static-side');
  }
 });
});
</script>

デモページ