body { position : relative | absolute | fixed; }

body 要素のデフォルトスタイルは position:static なのだが、サイトによっては position:relative とかが付いていたりする。以下では、そういうサイトで起こる問題を説明する。


例えば何か適当な要素をドキュメントの一番左上に置くとき、その要素の position を absolute にして、top と left で座標を指定することがある。特に JavaScript で動的に作る要素はこういうことをする場合が多い。左上に限らず、マウスでクリックした位置に要素を置く場合の常套手段でマウスイベントの pageX と pageY を使ったりもする。

そういうとき、body の position が static なら

のようにちゃんとドキュメントの左上基準で要素が置かれるところが、relative や absolute だったら、

のように body の左上基準で計算されることになる。fixed の場合は absolute の場合と同じような見た目になる。

これは CSS の absolute の意味を考えれば当然のことである。

absolute

絶対位置への配置となります。親ボックスにpositionプロパティのstatic以外の値が指定されている場合には、親ボックスの左上が基準位置となります。親ボックスにpositionプロパティのstatic以外の値が指定されていない場合には、ウィンドウ全体の左上が基準位置となります。

CSS : positionの「absolute」「relative」「fixed」のリファレンス | CSS Lecture

通常、body に position:static 以外が付いていることはほとんど無いため、JavaScript を書くときなどにハマることになる。例えば「はてなスター」では、スターの上にマウスを持ってくるとユーザー名が出るのだが、それがずれて表示される。


ドキュメントの左上基準で要素を置きたいときはこの「ずれ」を差し引いて要素を置く必要がある。残念ながら CSS だけでこの「ずれ」を算出する方法は無さそうだ。具体的には body の margin-left と margin-top を足してやればいいのだが、body {width:800px; margin-left:auto; margin-right:auto;} のようなよくあるセンタリングの場合はおそらくお手上げである。


JavaScript を使った補正の方法は、とりあえず標準モードのときはこのようなものになる (OperaChromeFirefox で確認)。互換モードでは Opera だけ border-top 補正が必要なくなるっぽい。IE8 以下では getComputedStyle が使えず、もしそれを currentStyle で代用したとしても border が em 単位などで指定されていたら使えないはずである。もっと良い方法があれば指摘してもらいたい。

function placeBox(x, y, width, height) {
  var rect1 = document.documentElement.getBoundingClientRect();
  var rect2 = document.body.getBoundingClientRect();
  var style = getComputedStyle(document.body, null);

  var bodyBorderTop = parseFloat(style.borderTopWidth);
  var bodyBorderLeft = parseFloat(style.borderLeftWidth);

  var topCorrection = rect1.top - rect2.top - bodyBorderTop;
  var leftCorrection = rect1.left - rect2.left - bodyBorderLeft;

  var div = document.createElement('div');
  div.setAttribute('style', 'position:absolute; background-color:green; opacity:0.5;');
  div.style.width = width + 'px';
  div.style.height = height + 'px';
  div.style.top = y + topCorrection + 'px';
  div.style.left = x + leftCorrection + 'px';
  document.body.appendChild(div);
}

body や html に border が付いている場合もカバーしているはずである。

デモ。

IE9pp でも見てみた。若干ずれてるが原因はよくわからない。