スクロールすることによってサイドカラムに配置しているナビゲーションメニューを
画面上部に固定してスクロールに合わせて追従させる動作はよく見かけますが
だいたいが画面上部に固定のみ、もしくは画面下部に固定のみ、といったものが多く
ブラウザウィンドウの高さよりもナビゲーションメニューの高さが大きかった場合には
ナビゲーションメニューが見切れてしまうものが多く見られます。

そんなスクロール追従型のナビゲーションで
メニューエリアが見切れないようにスクロールに合わせて
画面に固定されつつメニュー部分だけをスクロールさせる
固定型ナビゲーションメニューをjQueryを使って作ってみたのでご紹介してみます。

jQueryで画面から見切れない固定型ナビゲーションメニューを実装する方法

「jQueryで画面から見切れない固定型ナビゲーションメニューを実装する方法」サンプルを別枠で表示

なかなか文章で説明するのも難しいので
サンプル動作からご確認ください。

サンプル画面左側のナビゲーションメニューは
スクロールに合わせて追従します。

ブラウザウィンドウサイズの高さをナビゲーションメニューよりも小さくすると
そのウィンドウの高さに合わせてメニュー部分のみがスクロールし
スクロールがメニュー上部に達したら画面上部に固定され
スクロールがメニュー下部に達したら画面下部に固定されます。

ウィンドウの高さに合わせてメニュー部分のみをスクロールさせることで
ナビゲーションメニューが画面から見切れてしまい
ボタン操作ができなくなる、ということを防ぎます。

この固定型ナビゲーションメニューの
全体構成についてまずはHTMLから。

◆HTML
<div id="wrapper">

<div id="contents">

<p>牧場のうしろはゆるい丘になって、その黒い平らな頂上は、北の大熊星の下に、ぼんやりふだんよりも低く連って見えました。</p>

<p>ジョバンニは、もう露の降りかかった小さな林のこみちを、どんどんのぼって行きました。まっくらな草や、いろいろな形に見えるやぶのしげみの間を、その小さなみちが、一すじ白く星あかりに照らしだされてあったのです。草の中には、ぴかぴか青びかりを出す小さな虫もいて、ある葉は青くすかし出され、ジョバンニは、さっきみんなの持って行った烏瓜のあかりのようだとも思いました。</p>

・・・・・中略・・・・・

</div><!-- /#contents -->

<nav id="menuList">
<ul>
<li><a href="#">≫ MENU 1</a></li>
<li><a href="#">≫ MENU 2</a></li>
<li><a href="#">≫ MENU 3</a></li>
<li><a href="#">≫ MENU 4</a></li>
<li><a href="#">≫ MENU 5</a></li>
<li><a href="#">≫ MENU 6</a></li>
<li><a href="#">≫ MENU 7</a></li>
<li><a href="#">≫ MENU 8</a></li>
<li><a href="#">≫ MENU 9</a></li>
<li><a href="#">≫ MENU 10</a></li>
</ul>
</nav><!-- /#menuList -->

</div><!-- /#wrapper -->

追従させるナビゲーションメニューエリアを
任意のブロック要素で囲いIDもしくはクラスを付けます。
(サンプルではID「#menuList」としています。)

併せて必要になる要素として
ナビゲーションメニューエリア全体を覆い
スクロールで追従させる範囲を決めることになる
ブロック要素を設置します。
(サンプルではID「#wrapper」としています。)

今回の追従型ナビゲーションでは
この2つのブロック要素が最低限必要になります。

サンプルにあるID「#contents」はサンプル画面を形成する要素となり
スクリプト動作には直接影響しないものになります。

これらの要素に対してCSSは以下のように指定します。

◆CSS
/* ------------------------------
	#wrapper
------------------------------ */
#wrapper {
	margin: 80px auto;
	width: 900px;
	text-align: left;
	position: relative;
}

/* ------------------------------
	#contents
------------------------------ */
#contents {
	width: 650px;
	float: right;
	text-align: left;
}

#contents p {
	padding-bottom: 2em;
	font-size: 1em;
	line-height: 1.8em;
	text-align: left;
}

/* ------------------------------
	#menuList
------------------------------ */
#menuList {
	width: 200px;
	float: left;
	-webkit-transform: translate3d(0, 0, 0);
	-webkit-backface-visibility: hidden;
	backface-visibility: hidden;
}

#menuList > ul {
	border-top: #555 1px solid;
	border-left: #555 1px solid;
}

#menuList > ul > li {
	border-right: #555 1px solid;
	border-bottom: #555 1px solid;
}

#menuList > ul > li > a {
	padding: 15px 25px;
	font-weight: bold;
	display: block;
	text-align: left;
	background: #eee;
	border: #fff 1px solid;
}

#menuList > ul > li > a:hover {
	background: #fff;
}

/* ------------------------------
	footer
------------------------------ */
footer {
	background: #eee;
	text-align: center;
}

footer p {
	padding: 100px 0;
}

footer h2 {
	color: #fff;
	background: #000;
}

footer h2 a:link,
footer h2 a:visited,
footer h2 a:active {
	color: #fff;
}

/* ------------------------------
	ClearFix
------------------------------ */
#wrapper:after {
	content: " ";
	display: block;
	clear: both;
}

「#wrapper」を大枠として
中に入るナビゲーション要素「#menuList」を「float:left」で画面左に配置して
メインコンテンツ要素「#contents」を「float:right」で画面右に配置しています。

ナビゲーションエリアのスクロールした際の位置指定に関しては
スクリプト側から指定するのでCSSでは特に指定しません。

「#menuList」に指定している
「-webkit-transform」「-webkit-backface-visibility」「backface-visibility」
といったものに関しては、スクロールした際にナビゲーションが
がたつかないようにするための記述になります。

そして実際のスクロール動作スクリプトは以下の様になります。

◆SCRIPT
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script>
$(function(){
	var setMenu = $('#menuList'),
	setArea = $('#wrapper');

	function fixedNav(){
		var wrapTop = setArea.offset().top,
		wrapLeft = setArea.offset().left,
		wrapHeight = setArea.outerHeight(),
		menuTop = setMenu.offset().top,
		menuHeight = setMenu.outerHeight();

		var leftPos = wrapLeft - $(window).scrollLeft();

		if(wrapHeight > menuHeight) {
			if(($(window).scrollTop()) < wrapTop) {
				setMenu.css({position:'static', top:'auto', left:'auto'});
			} else {
				if($(window).height() > menuHeight){
					if(($(window).scrollTop()) > wrapTop && $(window).scrollTop() < (wrapTop + wrapHeight - menuHeight)){
						setMenu.css({position:'fixed', top:'0', left: leftPos});
					} else if($(window).scrollTop() > (wrapTop + wrapHeight - menuHeight)){
						setMenu.css({position:'absolute', top:wrapHeight - menuHeight, left:'0'});
					}
				} else {
					if(($(window).scrollTop()) > wrapTop && $(window).scrollTop() + $(window).height() < wrapTop + wrapHeight){
						var posTop = setMenu.offset().top;

						if($(window).scrollTop() < posTop){
							setMenu.css({position:'fixed', top:'0', left: leftPos});
						} else {
							if($(window).scrollTop() + $(window).height() > posTop + menuHeight){
								setMenu.css({position:'absolute', top:$(window).scrollTop() - (menuHeight - $(window).height()) - wrapTop, left:'0'});
							} else {
								setMenu.css({position:'absolute', top:posTop - wrapTop, left:'0'});
							}
						}
					} else if($(window).scrollTop() + $(window).height() > wrapTop + wrapHeight){
						setMenu.css({position:'absolute', top:wrapHeight - menuHeight, left:'0'});
					}
				}
			}
		}
	}

	$(window).on('load scroll resize', function(){
		fixedNav();
	});
});
</script>

スクリプト開始部分にある設定値の内容は以下の様になっています。

setMenu = $(‘#menuList’) ナビゲーションメニュー全体を囲うブロック要素(クラスでも可)
setArea = $(‘#wrapper’) ナビゲーションメニュー全体を覆い追従エリアとなるブロック要素(クラスでも可)

スクロールすることで、スクロール位置とナビゲーションメニュー位置および高さを判別して
メニューエリアの「position」プロパティを「fixed」と「absolute」に切り替え、
「top」の値をその都度調整することによって動作させています。

ナビゲーションエリアの高さとウィンドウの高さを比較して
ウィンドウの高さが小さい場合は、その高さに合わせて
ナビゲーションエリアを上下にスクロールさせるようにしています。

スクリプト全体を「fixedNav()」の名前で関数にしているのは
ナビゲーションメニューにアコーディオン動作などが付いていた場合に
そのクリック動作でナビゲーションエリアの高さが変化することになり、
追従位置の指定がずれてしまう可能性があるので
そういった場合に、この「fixedNav()」を同時に実行させるようにすることで
クリック動作と合わせて再度このスクリプトを実装させることができ、
位置の再調整が可能になる構成にしています。

レスポンシブ版

一つ目のサンプルはPCレイアウトのみの指定になっていますが
実際はレスポンシブで構成されたWebサイトで使用することの方が多くなると思うので
ウィンドウサイズによってスクロール動作の実行を制御したパターンも紹介してみます。

「レスポンシブ版:jQueryで画面から見切れない固定型ナビゲーションメニューを実装する方法」サンプルを別枠で表示

ブレークポイントを「768px」で設定し、
ウィンドウサイズがそれ以上だったらスクロール動作のスクリプトを実行し、
ウィンドウサイズがそれ以下だったらスクロール動作のスクリプトを実行させません。

このサンプルではHTMLは一つ目のものと同じになるので割愛します。

CSSではPCレイアウト時の構成はそのままで
ブレークポイントを「768px」としてメディアクエリーを使って
スマートフォンレイアウト時の指定のみ加えます。

◆CSS
/* ------------------------------
	MediaQueries
------------------------------ */
@media only screen and (max-width: 768px) {
	body {
		min-width: 100%;
	}

	#wrapper {
		margin: 40px auto 0 auto;
		width: 100%;
	}

	#contents {
		width: 100%;
		float: auto;
	}

	#contents p {
		padding: 0 5% 2em;
	}

	#menuList {
		width: 100%;
		float: auto;
	}

	#menuList > ul {
		border-left: none;
	}

	#menuList > ul > li {
		border-right: none;
	}

	#menuList > ul > li > a {
		padding: 25px 15px;
	}
}

スクリプト動作に直接影響するものではないですが
メディアクエリーを使って通常のレスポンシブに関わる指定を加えます。

そして、レスポンシブに対応したスクリプトは
以下の様な構成になります。

◆SCRIPT
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.12.0/jquery.min.js"></script>
<script>
$(function(){
	var setMenu = $('#menuList'),
	setArea = $('#wrapper'),
	breakPoint = 768;

	function fixedNav(){
		var wrapTop = setArea.offset().top,
		wrapLeft = setArea.offset().left,
		wrapHeight = setArea.outerHeight(),
		menuTop = setMenu.offset().top,
		menuHeight = setMenu.outerHeight();

		var leftPos = wrapLeft - $(window).scrollLeft();

		if(wrapHeight > menuHeight) {
			if(($(window).scrollTop()) < wrapTop) {
				setMenu.css({position:'static', top:'auto', left:'auto'});
			} else {
				if($(window).height() > menuHeight){
					if(($(window).scrollTop()) > wrapTop && $(window).scrollTop() < (wrapTop + wrapHeight - menuHeight)){
						setMenu.css({position:'fixed', top:'0', left: leftPos});
					} else if($(window).scrollTop() > (wrapTop + wrapHeight - menuHeight)){
						setMenu.css({position:'absolute', top:wrapHeight - menuHeight, left:'0'});
					}
				} else {
					if(($(window).scrollTop()) > wrapTop && $(window).scrollTop() + $(window).height() < wrapTop + wrapHeight){
						var posTop = setMenu.offset().top;

						if($(window).scrollTop() < posTop){
							setMenu.css({position:'fixed', top:'0', left: leftPos});
						} else {
							if($(window).scrollTop() + $(window).height() > posTop + menuHeight){
								setMenu.css({position:'absolute', top:$(window).scrollTop() - (menuHeight - $(window).height()) - wrapTop, left:'0'});
							} else {
								setMenu.css({position:'absolute', top:posTop - wrapTop, left:'0'});
							}
						}
					} else if($(window).scrollTop() + $(window).height() > wrapTop + wrapHeight){
						setMenu.css({position:'absolute', top:wrapHeight - menuHeight, left:'0'});
					}
				}
			}
		}
	}

	$(window).on('load scroll resize', function(){
		if(window.innerWidth > breakPoint){
			fixedNav();
		} else {
			setMenu.removeAttr('style')
		}
	});
});
</script>

スクリプト開始すぐの「breakPoint = 768」が
レスポンシブさせるブレイクポイントの値になるので
CSSのメディアクエリーで指定した値と同じものを入れておきます。

スクロールでの追従動作に関しては一つ目のスクリプトと同じになり
スクリプト50~54行目でウィンドウサイズ幅がブレイクポイントより大きければ
スクリプトを実行させ、小さければ実行させずに
それまでに入ってしまっていた値となるstyle属性を削除する構成になっています。

ナビゲーションエリア右サイド版

ここまでのサンプルはナビゲーションエリアがページの左側にあるタイプでしたが
その逆でナビゲーションエリアが右側にある場合はスクリプトの構成が少し変わってきます。

HTML、CSS、スクリプトの基本構成は左タイプと同じなので詳しい解説は省きますが
ナビゲーションエリアが右に配置されたタイプのサンプルを
レスポンシブ有無の2パターン紹介しておきます。

「ナビ右タイプ版:jQueryで画面から見切れない固定型ナビゲーションメニューを実装する方法」サンプルを別枠で表示

「ナビ右タイプ レスポンシブ版:jQueryで画面から見切れない固定型ナビゲーションメニューを実装する方法」サンプルを別枠で表示

以上がjQueryを使って画面から見切れることのない
固定型ナビゲーションメニューを実装する方法でした。

縦に長い固定型ナビゲーションメニューの設置が必要な場合に
この様な動作仕様にしておくことでユーザーにストレスを感じさせることなく
サイト内の各ページへ遷移させる導線を確保できるかと思います。

画面から見切れることのない固定型ナビゲーションメニューを実装する際にぜひ。。。

サンプルファイルをダウンロードしたい方はこちらから