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

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

デモページ

このサイドバーの概要

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

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

fixed-side-image

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>

デモページ