Bootstrap 2.1「Affixプラグイン」がわかりにくかったので少し紐解いてみました

Bootstrap 2.1 から追加されたJavaScriptプラグイン「Affix」とは、

スクロールに追従するメニューや要素を実装できるプラグイン

Bootstrapのサイトでも実装されている。
以下の赤で囲った部分がAffixになっている。

Affix要素の位置まで通常スクロール。それを越えるとAffix要素が追従する。そして下の要素(フッター)の前で止まる。

まずはドキュメントを読んで実装

ドキュメント: Bootstrap > JavaScript > Affix

1. Affixプラグインを読み込んで「data-spy=”affix”」を記述

<div data-spy="affix">...</div>

動作サンプル1

・スクロールに追従するが位置が完全に固定される(上の要素の高さ分の余白が常にある状態)
・下の要素を突き抜けてしまう

2. data-offset-top を指定

<div data-spy="affix" data-offset-top="200">...</div>

動作サンプル2

・スクロール量が data-offset-top(=200)に達したら追従を開始する
・追従を開始すると余白が発生する(上の要素の高さ分の余白)
・下の要素を突き抜けてしまう

というように、なかなかうまくいかない。

結局、マークアップだけではなく、CSSとaffixメソッドで制御しなければならないっぽい。

CSSとaffixメソッドで実装

まずは成功っぽい例から

動作サンプル3

<div class="my_affix">
 ...
</div>
$('.my_affix').affix({
	offset: {
		top: 200,
		bottom: 220
	}
});
・offset:top は、上の要素の高さ(ヘッダ(=200)を指定
・offset:bottom は、下の要素の高さ(フッタ(=200)+余白(=20))を指定
・.affix は「position: fixd」になるので、「top: 0」を指定して、上の要素の高さを相殺しないようにする
・.affix-bottom は「position: absolute」で、「bottom: 220px(=offset: bottom)を指定する。

仕組みは以下

Affixプラグインの仕組みを図解

まず、Affix要素はスクロール量とoffset引数によって、「.affix-top → .affix → .affix-bottom」と、クラスが自動的に変化していく。

offset引数(offset:top, offset:bottom)は以下の高さを指定する。

そうすると、状態変化と共にクラスが変化していく。

という仕組みがわかれば、さきほどの動作サンプル3でいいように見える。

でもこれは不完全。
正確には、”BootstrapのCSSを読みこんでいる場合”は不完全。

Bootstrapのサイトで実装されているAffixへの疑問

BootstrapのCSSを読み込んでいる場合、これだけではうまくいかないはず。

Affixが発動すると親要素の高さがゼロになるはず

追従を開始する(.affix)と、通常フローから逸脱する、つまり「position: fixed」になるで、Affix要素と並列の要素がないと親要素の高さがゼロになってしまう。
よって、親要素がグリッドレイアウトだった場合、並列のグリッドレイアウトが左寄せになってしまうはず。

少なくとも、Bootstrap2.1ではそうなるはず。

だが、現在のBootstrapのサイトではちゃんと動いているように見える。
なんでだろう?と思ったら、姑息なことをしていた…

[class*="span"] {
	...
	min-height: 1px;
}

「min-height: 1px;」とはなかなかスマートではない…

それよりもなによりも、ドキュメントには記載されていないカスタムCSSを書いてるのか!?という疑惑。

Bootstrap2.1.1のCSSには「min-height: 1px」が確かに記述されている。Bootstrap2.1.0にはない。
なるほど。どうやら、しれっとバグフィックスした模様。

スマートな方法ではないが、とりあえずはこれはOK。

グリッドレイアウトの可変幅にはフィットしないはず

Affix要素は「position: fixed」になるので、親要素が可変グリッドだったとしても、その幅にはフィットしない。
Bootstrapのサイトではどうなっているかというと、結局、親要素にクラスを振って、幅をpx固定している。

さらに、Bootstrapはレスポンシブデザインで設計されているので、画面サイズによってAffix要素の横幅も流動的に変化していくので、Affix要素もメディアクエリごとに細かく指定している模様。

レスポンシブを前提に実装するなら仕方ないが、Affixプラグインだけを使いたい場合はけっこうな手間。

ということで、BootstrapのレスポンシブCSSは使わず、あくまでもAffixプラグインだけ使うパターン↓

BootstrapのCSSを読み込まず、Affixプラグインだけを使うパターン

動作サンプル4

<!DOCTYPE HTML>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>Bootstrap Affix</title>
	<style type="text/css">
	body {
		position: relative;
		margin: 0;
	}
	.header, .footer {
		height: 200px;
		background: #e3e3e3;
	}
	.container {
		width: 960px;
		margin: 0 auto;
	}
	.left_column {
		float: left;
	}
	.right_column {
		padding: 20px 0 20px 220px; 
	}
	.content {
		background: #eee;
	}
	.my_affix {
		margin: 20px 0 0;
		width: 200px;
		background: #eee;
		float: left;
	}
	.my_affix.affix {
		position: fixed;
		top: 0;
	}
	.my_affix.affix-bottom {
		position: absolute;
		top: auto;
		bottom: 220px;
	}
	</style>
</head>

<body>

	<header class="header">
	</header>
	
	<div class="container">
		<div class="left_column">
			<div class="my_affix">
			 ...
			</div>
		</div>
		<div class="right_column">
			<div class="content">
			 ...
			</div>
		</div>
	</div>

	<footer class="footer">
	</footer>

	<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
	<script src="./js/bootstrap.js"></script>
	
	<script type="text/javascript">
	$(function(){
		$('.my_affix').affix({
			offset: {
				top: 200,
				bottom: 220
			}
		});
	});
	</script>

</body>
</html>

まとめ

・Affixプラグインを実装するには、マークアップだけではダメで、CSSとaffixメソッドを使う必要がある。
・offset引数は必ず指定した方がいい。
・スクロール量とoffset引数によって、「.affix-top → .affix → .affix-bottom」とクラスが自動的に変化していく。
・クラス変化=状態変化に応じて、CSSで制御する必要がある。

個人的は意見としては、レスポンシブデザインをベースに開発されているBootstrapのJavaScriptプラグインなのに、レスポンシブに完全に対応できていないAffixプラグインはどうなの?という感じはちょっとありますね。

Comments

コメントを残す