Apple Store のサイドバーのように、要素をウィンドウのスクロールに追従させる jQuery プラグイン、jQuery Floating Widget を作りました。説明が難しいので、まずはデモをご覧ください!
このアイデア自体は新しいものではなくて、たとえば以下の記事で詳しく紹介されています:
この方法で基本的にうまくいくんですが、フッターの高さがある程度あると、下までスクロールしたときに該当要素がフッター領域に食い込んでしまう場合があります。そこで、上記記事での実装を参考に、要素の「動ける範囲」を制限するための処理などを加えたものを考えてみました。
例として、フロートによる 2 カラムのレイアウトで、サイドバーをスクロールに追従させる場合をもとに解説します。マークアップと基本的なスタイルは以下のとおりです:
<div class="header"> . . . </div>
<div class="content-wrapper">
<div class="content"> . . . </div>
<div class="sidebar">
<div class="floating-widget">
<!-- この要素がスクロールについてきます -->
</div>
</div>
</div>
<div class="footer"> . . . </div>
.content-wrapper {
position: relative;
*zoom: 1;
}
.content-wrapper:after {
display: block;
clear: both;
height: 0.01px;
content: "";
}
.content {
float: left;
width: 512px;
}
.sidebar {
float: right;
width: 256px;
}
.floating-widget {
margin: 50px 0;
}
該当要素の動ける範囲を制限する祖先要素 (div.content-wrapper
) には、スクロールが下まで来たときに該当要素を絶対配置するための position: relative;
と、高さを取得するための clearfix が必要です。
プラグイン本体とその呼び出し方は以下のとおりです:
// プラグイン
(function ($) {
$.fn.floatingWidget = function () {
return this.each(function () {
var $this = $(this),
$parent = $this.offsetParent(),
$window = $(window),
top = $this.offset().top - parseFloat($this.css('marginTop').replace(/auto/, 0)),
bottom = $parent.offset().top + $parent.height() - $this.outerHeight(true),
floatingClass = 'floating',
pinnedBottomClass = 'pinned-bottom';
if ($parent.height() > $this.outerHeight(true)) {
$window.scroll(function () {
var y = $window.scrollTop();
if (y > top) {
$this.addClass(floatingClass);
if (y > bottom) {
$this.removeClass(floatingClass).addClass(pinnedBottomClass);
} else {
$this.removeClass(pinnedBottomClass);
}
} else {
$this.removeClass(floatingClass);
}
});
}
});
};
})(jQuery);
// 呼び出し
$(function () {
$('.floating-widget').floatingWidget();
});
ウィンドウのスクロール位置を見て、要素がスクロールに追従している状態 (.floating
) と祖先要素の下端にとどまっている状態 (.pinned-bottom
) というそれぞれの状態に応じてマークアップのクラスを書き換えています。また最初に該当要素と祖先要素の高さを比較し、サイドバーの高さがコンテンツを超えるような場合はなにも起こりません。
ちなみに jQuery の offsetParent()
は position
プロパティが static
以外である直近の祖先要素を返すメソッドです。また outerHeight()
メソッドはパディングとボーダーを含む要素の高さを返しますが、引数に true
を渡すとさらにマージンも含めた高さを返します。
最後に、.floating
と .pinned-bottom
というクラスに対してスタイルを追加します:
.floating-widget.floating {
position: fixed;
top: 0;
}
.floating-widget.pinned-bottom {
position: absolute;
bottom: 0;
_position: static;
}
スクロールのたびにスクリプトでスタイルを書き換える方法もありますが、それだと動きがカクカクしがちなので、JavaScript の仕事は位置によってマークアップのクラスを変更するだけにして、配置は CSS にまかせるというアプローチを採りました。
position: fixed;
を解釈しない IE6 ではスクロールに追従しませんが、下まで来ると position: absolute;
が適用されて要素が突然移動してしまうので、ハックで static
を上書きしています。
ソースコードは以下からどうぞ。恥ずかしながら GitHub デビューしました!