jQueryで途中で止まって途中で動き始めるサイドバーの実装方法

HRナビ by リクルートのようなサイドバーを作成してみました。

まずはごくごく普通の2カラムのデザインを用意します。

<body>
  <div id ="wrap">
   <div id="main">
     <div id ="main-inner">
     mainmainmainmainmainmainmainmainmainmainmainmain
     </div>
   </div>
   <div id ="side">
     <div id ="sideFirst">
        FirstFirstFirstFirstFirstFirstFirstFirstFirst
     </div>
     <div id ="sideSecond">
        secondsecondsecondsecondsecondsecondsecond
     </div>
     <div id ="sideThird">
     thirdthirdthirdthirdthirdthirdthirdthirdthird
     </div>
   </div>
 </div>
</body>

cssはこんな感じ

#wrap{
	width:1100px;
	height:3000px;
	margin:100px 100px;
}

#main{
   width:700px;
   height:3000px;
   margin-right:400px;
   float:left;
   background:red;
}

#main-inner{
	padding-right:30px;
	width:670px;
}

#side{
	width:350px;
    height:1500px;
	float:left;
	margin-left:-350px;
	background:#ccc;
}

#sideFirst{
	height:280px;
	width:300px;
	margin:10 auto;
	background:#2CAEA6;
}

#sideSecond{
	height:280px;
	width:300px;
	margin:10 auto;
	background:#307FB8;
}

#sideThird{
	height:280px;
	width:300px;
	margin:10 auto;
	background:#F0F7FC;
}

画像で見てみるとメインカラムに対してサイドカラムの方が短いことが分かります。
f:id:inoue510:20151223185908p:plain

このままだとページの後半部分は空白になってしまい、リンクで次のページを訴求することができません。ここでは#sideSecondに人気記事のリストがあると仮定して、ページの中間部分で#sideSecondを固定し、メインカラムのbottomに合わせてサイドカラムが再び動き出すようにします。

まずは記事の途中でサイドカラムが固定されるように設定します。
cssに次の要素を追加します。

#side.is-fixed{
	position: fixed;
	top:0px;
	z-index: 100;
}

続いてjsファイルはこんな感じに

$(function(){
	var $main = $('#main'), $side = $('#side'), $sideSecond = $('#sideSecond');//各種要素取り出し
	/*#main のTOP、高さ、bottom情報を取り出す*/
	var $mainTop =$main.offset().top;
	var $mainHeight = $main.height();
	var $mainBottom = $mainTop + $mainHeight;
	/*サイドカラムの高さ、sサイドカラムの空白の高さを取り出す*/
	var $sideHeight = $side.height();
	var $paddingBottom = $mainHeight - $sideHeight;
    /*二つ目のコンテンツのtopの位置を取り出し、サイドカラムの固定する起点にする*/
	var $breakStart = $sideSecond.offset().top;
    /*サイドカラムをそのまま固定すると、サイドカラムの頂点が固定されるのでその差分を取る、横もいい感じの場所に変更しましょう*/
	var $fixedTop = ($breakStart - $mainTop)*(-1);
	var $fixedLeft = $side.offset().left + $side.width();

	$(window).on('scroll', function(){
		if( $breakStart < $(this).scrollTop()){
			$side.addClass('is-fixed');
			$side.css({top:$fixedTop});
			$side.css({left:$fixedLeft});
		}else{
			$side.removeClass('is-fixed');
		}
	});
});

$(this).scrollTop()で現在の位置を取得できるので、

$breakStart = $sideSecond.offset().top

で設定したサイドカラムの二つ目のてっぺんをスクロールで通り越したら、

$side.addClass('is-fixed');

で先に設定したcssファイル適用できるようします。なお

$side.removeClass('is-fixed');

を記述しないと、上にスクロールを戻しても固定したままになってしまうので注意しましょう。


ここまでである地点までで到達するとサイドバーが固定されるようになりました。
続いて固定されたサイドバーが再び動き出すようにソースコードを追加していきます。

まずは#breakEndを指定して固定する終点を設定する必要があります。
#breakEndはサイドカラムとメインカラムの差分を取ったものを設定すると上手にサイドカラムとメインカラムのbottomが合います。

変数を追加してこんな感じに

var $main = $('#main'), $side = $('#side'), $sideSecond = $('#sideSecond');
	var $mainTop =$main.offset().top;
	var $mainHeight = $main.height();
	var $mainBottom = $mainTop + $mainHeight;
	var $sideHeight = $side.height();
	var $paddingBottom = $mainHeight - $sideHeight;
	var $breakStart = $sideSecond.offset().top;
	var $breakEnd = $breakStart + $paddingBottom;
	var $fixedTop = ($breakStart - $mainTop)*(-1);
	var $fixedLeft = $side.offset().left + $side.width();

$paddingBottomでメインカラムとサイドカラムの差分を出しているのですが、$paddingBottomをそのまま$breakEndにすると$breakStartより遥かに小さい値になってしまうので、$breakStartと$paddingBottom足した値にしましょう。

var $breakEnd = $breakStart + $paddingBottom;

このbreakEndを利用して条件さらに制限します。

$(window).on('scroll', function(){
		if( $breakStart < $(this).scrollTop() && $(this).scrollTop() < $breakEnd){
			$side.addClass('is-fixed');
			$side.css({top:$fixedTop});
			$side.css({left:$fixedLeft});
		} else{
			$side.removeClass('is-fixed');
		
		}
	});

しかしこのままだと$breakEndを通過した後は、空白になってしまい、#sideThirdを見ることができません。
そこでelse if($(this).scrollTop() > $breakEnd)の条件も追加しましょう。
今回はサイドバーを固定したまま、#sideのtopを変更してあたかもサイドバーがスクロールしているかのように見せます。

if( $breakStart < $(this).scrollTop() && $(this).scrollTop() < $breakEnd){
			$side.addClass('is-fixed');
			$side.css({top:$fixedTop});
			$side.css({left:$fixedLeft});
		}else if($(this).scrollTop() > $breakEnd){
		    var $plusTop = $breakEnd - $(this).scrollTop() + $fixedTop;
		    $side.addClass('is-fixed');
		    $side.css({top:$plusTop});
		} else{
			$side.removeClass('is-fixed');
		}
	});
});
 var $plusTop = $breakEnd - $(this).scrollTop() + $fixedTop;

でスクロールした分だけ、#sideのtopの位置を変更します。

これで実装完了です。



かなり雑な実装になってしまいましたが、一応動くものは作れました。リサイズしたときにサイドカラムの位置がずれる等の問題があると思うので、ご自分で改善してみて下さい。では