要素を移動させるためには、座標系の仕組みを理解しておく必要があります。
ほとんどの JavaScript メソッドは2つの座標系のいずれかを扱います:
- ウィンドウ相対座標 –
position:fixedと同様で、ウィンドウの上端/左端を基準として計算されます。- これらの座標は
clientX/clientYと表記します。このような名前の理由については、イベントプロパティについて学習する際に明らかになります。
- これらの座標は
- ドキュメント座標 – ドキュメントルートにおける
position:absoluteと同様で、ドキュメントの上端/左端を基準として計算されます。- これらの座標は
pageX/pageYと表記します。
- これらの座標は
ページを一番最初までスクロールして、ウィンドウの左上端がドキュメントの左上端と完全に一致した状態であれば、これらの座標は互いに等しくなります。しかし、ドキュメントがスクロールされると、要素がウィンドウ内を移動するため、要素のウィンドウ相対座標は変化しますが、ドキュメント相対座標は変わりません。
この画像では、ドキュメント内の特定の一点を、スクロール前(左)とスクロール後(右)の座標で示しています。
ドキュメントがスクロールされた場合:
pageY– ドキュメント相対座標は変化しません。これはドキュメントの最上部(すでにスクロールアウトした部分)を基準に数えられています。clientY– ウィンドウ相対座標は変化します(矢印が短くなっています)。同じ点がウィンドウの最上部に近づいたためです。
要素の座標取得: getBoundingClientRect()
elem.getBoundingClientRect()メソッドは、elemを囲った最小の長方形のウィンドウ座標を、組み込みの DOMRect クラスのオブジェクトとして返します。
DOMRectの主要なプロパティ:
x/y– ウィンドウに対する、長方形の原点のX/Y座標。width/height– 長方形の幅/高さ(負の値も可)。
さらに、派生したプロパティもあります:
top/bottom– 長方形の上端/下端のY座標。left/right– 長方形の左端/右端のX座標。
例えば、このボタンをクリックして、そのウィンドウ座標を見てください。
繰り返しページをスクロールしていると、ウィンドウ内のボタンの位置が変化するにつれて、そのウィンドウ相対座標(縦方向にスクロールした場合は y/top/bottom 座標)も変化することに気づくでしょう。
以下がelem.getBoundingClientRect()の出力の図です:
ご覧のとおり、x/yとwidth/heightで長方形を完全に表しています。これらから、派生プロパティを簡単に計算できます:
left = xtop = yright = x + widthbottom = y + height
注意点:
- 座標は
10.5のような小数になる場合があります。これは正常で、ブラウザは内部で計算に小数を使用しています。style.left/topを設定する際に、小数を丸める必要はありません。 - 座標が負の値になることもあります。例えば、ページをスクロールして
elemがウィンドウの上部にある場合、elem.getBoundingClientRect().topは負の値になります。
数学的には、長方形は開始点(x, y)と方向ベクトル(width, height)によって一意に定義されます。したがって、追加の派生プロパティは利便性のためのものです。
技術的には、width/heightが負の値になることも可能であり、これにより「方向性を持った」長方形(例えば、適切にマークされた開始点と終了点を持つマウスの選択範囲など)を表現できます。
width/heightの値が負の場合、長方形が右下端から始まり、左上に向かって「伸びる」ことを意味します。
以下に、負のwidthとheightを持つ長方形(例:width=-200、height=-100)を示します:
ご覧の通り、このような場合、left/topはx/yと等しくありません。
ただし、実際にはelem.getBoundingClientRect()は常に正のwidth/heightを返します。ここで負のwidth/heightに言及するのは、これらの一見重複しているプロパティが実際には重複していないことを理解していただくためです。
Internet Explorerは歴史的な理由からx/yプロパティをサポートしていません。
したがって、ポリフィルを作成する(DomRect.prototypeにゲッターを追加する)か、単にtop/leftを使用することができます。なぜなら、width/heightが正の値である場合、これらは常にx/yと等しいからです。特にelem.getBoundingClientRect()の結果がこれに該当します。
ウィンドウ相対座標とCSSのposition:fixedの間には、明白な類似点があります。
しかし、CSSの配置において、rightプロパティは右端からの距離を意味し、bottomプロパティは下端からの距離を意味します。
上記の図を見れば分かるように、JavaScriptではそうではありません。これらの座標も含め、すべてのウィンドウ座標は左上端から数えられます。
elementFromPoint(x, y)
document.elementFromPoint(x, y) の呼び出しは、ウィンドウ座標 (x, y) で最もネストされた要素を返します。
構文は次の通りです:
let elem = document.elementFromPoint(x, y);
例えば、以下のコードは、現在ウィンドウの中央にある要素のタグを強調表示して出力します。:
let centerX = document.documentElement.clientWidth / 2;
let centerY = document.documentElement.clientHeight / 2;
let elem = document.elementFromPoint(centerX, centerY);
elem.style.background = "red";
alert(elem.tagName);
ウィンドウ座標を使うので、要素は現在のスクロール位置に応じて異なります。
elementFromPoint は null を返します。メソッド document.elementFromPoint(x,y) は (x,y) が可視領域にある場合にのみ動作します。
もし座標のいずれかが負の値であるか、ウィンドウの幅/高さを超えている場合、null を返します。
チェックを怠ると、以下のような典型的なエラーが発生する可能性があります:
let elem = document.elementFromPoint(x, y);
// 座標がウィンドウ外の場合、elem = null
elem.style.background = ''; // エラー!
「fixed」配置の使用
ほとんどの場合、何かを配置するには座標が必要です。
要素の近くに何かを表示するには、getBoundingClientRectを使用してその座標を取得し、次にCSSのpositionとleft/top(またはright/bottom)を組み合わせて使用します。
例えば、以下のcreateMessageUnder(elem, html)関数は、elemの下にメッセージを表示します。
let elem = document.getElementById("coords-show-mark");
function createMessageUnder(elem, html) {
// メッセージ要素の作成
let message = document.createElement('div');
// ここでは、スタイルにCSSクラスを使う方が良いです
message.style.cssText = "position:fixed; color: red";
// 座標の設定, "px" を忘れないこと!
let coords = elem.getBoundingClientRect();
message.style.left = coords.left + "px";
message.style.top = coords.bottom + "px";
message.innerHTML = html;
return message;
}
// 使用例:
// ドキュメントに5秒間追加する
let message = createMessageUnder(elem, 'Hello, world!');
document.body.append(message);
setTimeout(() => message.remove(), 5000);
実行するにはボタンをクリックしてください:
このコードは、メッセージを左、右、下に表示したり、CSSアニメーションを適用して「フェードイン」させたりするように修正できます。要素のすべての座標とサイズが分かっているため、これは簡単です。
しかし、重要な点に注意してください。ページをスクロールすると、メッセージがボタンから離れていきます。
その理由は明白です。メッセージ要素がposition:fixedに依存しているため、ページがスクロールしてもウィンドウの同じ位置に留まろうとするからです。
これを変えるには、ドキュメント基準の座標とposition:absoluteを使用する必要があります。
ドキュメント座標
ドキュメント相対座標は、ウィンドウではなくドキュメントの左上端から始まります。
CSSでは、ウィンドウ座標はposition:fixedに対応し、ドキュメント座標はそれに近いposition:absoluteに対応します。
position:absolute と top/left を使用すると、ドキュメント内の特定の位置に要素を配置し、ページをスクロールしてもその位置に留まるようにすることができます。ただし、そのためにはまず正しい座標が必要です。
要素のドキュメント座標を取得するための標準メソッドはありません。しかし、それを記述するのは簡単です。
これら2つの座標系は、以下の式で結びつけられます:
pageY=clientY+ ドキュメントのスクロールアウトした垂直部分の高さ。pageX=clientX+ ドキュメントのスクロールアウトした水平部分の幅。
getCoords(elem)関数は、elem.getBoundingClientRect()からウィンドウ座標を取得し、現在のスクロール量を加算します:
// 要素のドキュメント座標を取得
function getCoords(elem) {
let box = elem.getBoundingClientRect();
return {
top: box.top + window.pageYOffset,
right: box.right + window.pageXOffset,
bottom: box.bottom + window.pageYOffset,
left: box.left + window.pageXOffset
};
}
もし上記の例でこれをposition:absoluteで使用した場合、メッセージはスクロールしても要素の近くに留まり続けます。
変更されたcreateMessageUnder関数は以下の通りです:
function createMessageUnder(elem, html) {
let message = document.createElement('div');
message.style.cssText = "position:absolute; color: red";
let coords = getCoords(elem);
message.style.left = coords.left + "px";
message.style.top = coords.bottom + "px";
message.innerHTML = html;
return message;
}
サマリ
ページ上のあらゆる点は、座標を持っています:
- ウィンドウに相対的 –
elem.getBoundingClientRect() - ドキュメントに相対的 –
elem.getBoundingClientRect()+ 現在のページスクロール
ウィンドウ座標はposition:fixedで使用するのが適しており、ドキュメント座標はposition:absoluteで使用するのが適しています。
どちらの座標系も長所と短所を持っており、CSS の position absolute と fixed のように、どちらか一方が必要なときがあります。
コメント
<code>タグを使ってください。複数行の場合は<pre>を、10行を超える場合にはサンドボックスを使ってください(plnkr, JSBin, codepen…)。