jQuery でブラウザの表示領域に対するサイズや位置情報を取得してみる
更新履歴
- 2010-01-21
- 本エントリの内容も含めた最新の情報は下記エントリをご参照ください。
前回、前々回のエントリではボックス要素を例にサイズや位置、スクロール量などの求め方について書きましたが、実際のプラグインの実装においてこれらの情報が必要になるのは、ブラウザの表示領域に対してということが多いかと思います。
具体的にはツールチップなどの機能で、画面の端の要素を hover した時、ポップアップがブラウザの表示領域内に収まるように表示位置を調整するような場合に、ブラウザの表示領域のサイズやスクロール量などが必要になります。
jQuery でブラウザの表示領域をつかむ方法
ブラウザの表示領域を jQuery でつかむには、以下のような記述でできそうです。
$('html') $(window) $(document)
DOM ノード上の先祖要素を
$('div.popup').parents()[$('div.popup').size()-1]
というふうに参照すると、html 要素が返ってくるので、プラグインとしての汎用性(擬似フレームの対応など)や保守性(コードの共通化)を考えると、$('html') のみで、諸々の情報が全て取得できたら理想的かと思われます。
位置情報の取得結果を比較してみる
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 の対応は断念しました。