jQuery でブラウザの表示領域に対するサイズや位置情報を取得してみる

更新履歴

2010-01-21
本エントリの内容も含めた最新の情報は下記エントリをご参照ください。


前回前々回のエントリではボックス要素を例にサイズや位置、スクロール量などの求め方について書きましたが、実際のプラグインの実装においてこれらの情報が必要になるのは、ブラウザの表示領域に対してということが多いかと思います。

具体的にはツールチップなどの機能で、画面の端の要素を hover した時、ポップアップがブラウザの表示領域内に収まるように表示位置を調整するような場合に、ブラウザの表示領域のサイズやスクロール量などが必要になります。

jQuery でブラウザの表示領域をつかむ方法

ブラウザの表示領域を jQuery でつかむには、以下のような記述でできそうです。

$('html')
$(window)
$(document)


DOM ノード上の先祖要素を

$('div.popup').parents()[$('div.popup').size()-1]

というふうに参照すると、html 要素が返ってくるので、プラグインとしての汎用性(擬似フレームの対応など)や保守性(コードの共通化)を考えると、$('html') のみで、諸々の情報が全て取得できたら理想的かと思われます。

主要ブラウザでサイズ・位置情報を取得してみる

IE6,IE7,IE8 Beta2,Firefox3,Safari3,Opera9.5 で、サイズ・位置情報を取得してみました。

確認用ページ

こちら

スクリーンショット
IE6(XP)

IE7(Vista)

IE8 Beta2(IETester)

Firefox3

Safari3

Opera9.5

ブラウザ間でかなり差異がでました。
汎用的に使うには独自拡張したラッパー・メソッドを書く必要がありそうです。
$(window)、$(document) ではほとんどの実行結果が、実行時エラーもしくは undefined でしたので、$('html') をベースに拡張し、$('html') では取得できない部分を $(window)、$(document) で補完するのがいいかと思います。

位置情報の取得結果を比較してみる

scrollTop()、scrollLeft() メソッド

まずは重要そうな スクロール量の取得から確認してみます。

$('html') $(window) $(document)
IE6 スクロール量 スクロール量 スクロール量
IE7 スクロール量 スクロール量 スクロール量
Firefox3 スクロール量 スクロール量 スクロール量
Safari3 常に 0 スクロール量 スクロール量
Opera9.5 スクロール量 スクロール量 スクロール量

Safari の $j('html') が、常に 0 になってしまいますので、以下のようにクロスブラウザ対応すればいいかと思います。

$.fn.exScrollTop = function(){
    return this.attr('tagName')=='HTML' ? $(window).scrollTop() : this.scrollTop();
}
position() メソッド

position() メソッドは CSS 的な意味での位置情報を返してくれます。詳細は前回のエントリを参照ください。
意図的にブラウザ表示領域に対し、このメソッドを使用するケースは無いかと思いますが、プラグインの実装などで、コンテナ要素を自動判別し且つ、コンテナ要素の表示位置を求めるといった事も想定できるので確認してみます。

$('html') $(window) $(document)
IE6 スクロール量 - 2 0 0
IE7 スクロール量 0 0
IE8 0 0 0
Firefox3 error error error
Safari3 error error error
Opera9.5 error error error

IE 以外では実行時エラーになりました。

IE7 の $('html') では scrollTop() などで求められるスクロール量と同じ値になりました。

IE6 の $('html') ではデフォルトで html に border-width:2px が設定されており、スクロール量からこの幅を引いた値が返されるようです。XP + IE6 で確認しました。
ちなみに Vista + IE Tester の IE6 で確認すると -2px はされないようです。(以下スクリーンショット参照)

クロスブラウザ対応は常に 0 を返すようにすればいいかと思います。

$j.fn.exPosition = function(){
	var o=this;
	return o.measur(function(){
		if(o.attr('tagName')=='HTML'||o[0]==window||o[0]==document)
			return {top:0,left:0};
		else{
			var pos=o.position();
			var container = o.exContainer();
			if(container.attr('tagName')=='HTML')return pos;
			return {
				top:pos.top+container.exScrollTop(),
				left:pos.left+container.exScrollLeft()
			}
		}
	})
}
offset() メソッド
$('html') $(window) $(document)
IE6 スクロール量 - 2 0 0
IE7 スクロール量 0 0
IE8 0 0 0
Firefox3 0 error error
Safari3 0 error error
Opera9.5 0 error error

position() メソッドとほぼ同じ結果ですが、IE 以外でも $('html') でエラーがでなくなりました。
こちらもクロスブラウザ対応は常に 0 を返すようにすればいいかと思います。

$j.fn.exOffset = function(){
    return this.attr('tagName')=='HTML'||this[0]==window||this[0]==document ?
        {top:0,left:0} : this.offset();
}

サイズの取得結果を比較してみる

どの範囲のサイズを取得してるのか分かりづらいので、取得箇所の図と対応させてまとめました。

ちなみに html 要素の border、padding 指定は省きました。(デフォルトのまま)
最初は指定して検証してたのですが、ブラウザ間の差異がはげしく検証がしんどっかたのでやめました。
クロスブラウザの対応については以下についてのみ考えてみます。

    • offsetHeight,offsetWidth
    • scrollHeight,scrollWidth
    • clientHeight,clientWidth
height(),width() メソッド
$('html') $(window) $(document)
IE6 h2:w2 h1:w1 h3:w3
IE7 h1:w1 h1:w1 h3:w3a
IE8 h3:w1 h2:w2 h3:w1
Firefox3 h3:w1 h1:w1 h3:w3a
Safari3 h3:w1 h2:w2 h3:w3a
Opera9.5 h3:w1 h3b:w3b h3:w3a

Opera9.5 の w1 と w3b は更にスクロールバーの幅を引いた値になってます。

かなりばらばらな結果になりました。
もはやのどのブラウザが正しい振る舞いをしてるのか分かりません。
とりあえず $('html')、$(window) で height() や width()をそのまま使うのはクロスブラウザ的に危険だったということが分かりました。

outerHeight(),outerWidth() メソッド
$('html') $(window) $(document)
IE6 h2:w2 h1:w1 h3:w3
IE7 h1:w1 h1:w1 h3:w3a
IE8 h3:w1 h2:w2 h3:w1
Firefox3 h3:w1 error error
Safari3 h3:w1 error error
Opera9.5 h3:w1 error error

IE 以外のブラウザで、$(window),$(document)が実行時エラーになる事意外は、height(),width() メソッドと同様の結果になりました。

innerHeight(),innerWidth() メソッド

outerHeight(),outerWidth() メソッドと同様の結果となりました。
html 要素に border を設定するとその幅分マイナスされた値が取得できるようです。

attr 系メソッド

$(window),$(document)だと全てのブラウザで実行時エラーになります。
$('html') の結果のみを表にまとめます。

offsetHeight系 scrollHeight系 clientHeight系
IE6 h2:w2 h2:w3 h1:w1
IE7 h1:w1 h3:w3a h1:w1
IE8 h3:w1 h3:w1 h2:w2
Firefox3 h3:w1 h3:w3a h1:w1
Safari3 h3:w1 h3:w3a h1:w1
Opera9.5 h3:w1 h3:w3a h1:w1


クロスブラウザ対応は、以下のような値が求まるようにしてみます。

offsetHeight h2
offsetWidth w2
scrollHeight h3
scrollWidth w3a(IE6 のみ w3)
clientHeight h1
clientWidth w1
//スクロールバー有無、サイズ取得
var isDisplayScrollBar=function(target,key){
	var val=target.css('overflow-'+key)
	if(val=='scroll')return true;
	if(val=='hidden')return false;
	if(val=='auto'||target.attr('tagName')=='HTML'){
		var method=(key=='y'?'Height':'Width');
		return target['exClient'+method]()< target['exScroll'+method]()
	}
	return false
}
$j.fn.isDisplayScrollBar=function(key){
	return isDisplayScrollBar(this,key)
}
var scrollBarWidth=function(target){
	var w=jQuery.browser.msie?16:jQuery.browser.safari?15:17;
	return {
		x : target.isDisplayScrollBar('x')?w:0,
		y : target.isDisplayScrollBar('y')?w:0
	}
}
$j.fn.scrollBarWidth=function(){
	return scrollBarWidth(this)
}
//非表示要素でも採寸できるようにする
$j.fn.measur=function(f){
	var o=this,ret;
	var hide=o.is(":hidden");
	if(hide)o.show();
	ret=f();	
	if(hide)this.hide();
	return ret;
}
//こっからがメイン
$j.fn.exClientHeight = function(){
	var o=(this[0]==window?$j('html'):this)
	return o.attr('clientHeight');
}
$j.fn.exClientWidth = function(){
	var o=(this[0]==window?$j('html'):this)
	var s=o.measur(function(){return o.attr('clientWidth')});
	if(o.attr('tagName')!='HTML')return s;
	if(jQuery.browser.msie && jQuery.browser.version==7)
		return o.width();
	return s;
}
$j.fn.exScrollHeight = function(){
	var o=this;
	var s=Math.max(
		o.measur(function(){return o.attr('scrollHeight')}),
		o.exClientHeight());
	if(o.attr('tagName')!='HTML')return s;
	if(jQuery.browser.msie && jQuery.browser.version==6)
		return $j(document).height();
	return s;
}
$j.fn.exScrollWidth = function(){
	var o=this;
	return Math.max(
		o.measur(function(){return o.attr('scrollWidth')}),
		o.exClientWidth());
}
$j.fn.exOffsetHeight = function(){
	var o=this;
	var s=o.measur(function(){return o.attr('offsetHeight')});
	if(o.attr('tagName')!='HTML')return s;
	if(jQuery.browser.msie && jQuery.browser.version==6)
		return s;
	return o.exClientHeight()+o.scrollBarWidth().x

}
$j.fn.exOffsetWidth = function(){
	var o=this;
	var s=o.measur(function(){return o.attr('offsetWidth')});
	if(o.attr('tagName')!='HTML')return s;
	if(jQuery.browser.msie && jQuery.browser.version==6)
		return s;
	return o.exClientWidth()+o.scrollBarWidth().y
}

$j.fn.scrollBarWidth でスクロールバーのサイズを取得してるのは、offsetWidth(offsetHeight)を求めるためで、clientHeight(clientWidth) にスクロールバーのサイズを加算した値を返しています。

実質、ブラウザの表示領域の情報としては、clientHeight(clientWidth)、scrollHeight(scrollWidth)があれば十分な気もしますが、プラグインの汎用処理で、$('html')で offsetHeight(offsetWidth)メソッドが動いてしまうケースがあるかもしれないので一応書いてみました。

attr系メソッドだと採寸対象要素が非表示だとサイズが取得できないので、measurメソッドで一時的に対象要素を表示状態にしています。

あと、w3 (通常 scrollWidth でとれる値) の幅が取れなかったので IE8 の対応は断念しました。