getBoundingClientRect で要素の位置とサイズを取得する

ある要素がブラウザのビューポート上でどこに表示されているか、どれくらいの大きさかを正確に知りたい場面は頻繁にあります。ツールチップの位置決め、要素同士の衝突判定、スクロール連動のアニメーションなど、レイアウト情報が必要になる場面は多岐にわたります。こうした要求に応えるのが getBoundingClientRect メソッドです。

基本的な使い方

getBoundingClientRect は、すべての要素が持つメソッドで、呼び出すと DOMRect オブジェクトを返します。

const box = document.getElementById('box');
const rect = box.getBoundingClientRect();

console.log(rect.top);    // ビューポート上端からの距離
console.log(rect.left);   // ビューポート左端からの距離
console.log(rect.width);  // 要素の幅
console.log(rect.height); // 要素の高さ

返される DOMRect には 8 つのプロパティが含まれています。

top要素上端のビューポート上端からの距離
right要素右端のビューポート左端からの距離
bottom要素下端のビューポート上端からの距離
left要素左端のビューポート左端からの距離
width要素の幅(right - left)
height要素の高さ(bottom - top)
xleft と同じ値
ytop と同じ値

ここで重要なのは、すべての値がビューポート基準だという点です。ページのスクロール量に関係なく、現在の画面上での見た目の位置を返します。たとえばページを 300px 下にスクロールした状態で取得すると、スクロール前と比べて top の値は 300 小さくなります。

ビューポート座標とページ座標の違い

getBoundingClientRect が返すのはビューポート座標です。ページ全体における絶対位置が欲しい場合は、スクロール量を加算する必要があります。

const rect = element.getBoundingClientRect();

// ビューポート座標(スクロールで変わる)
const viewportX = rect.left;
const viewportY = rect.top;

// ページ座標(スクロールしても変わらない)
const pageX = rect.left + window.scrollX;
const pageY = rect.top + window.scrollY;
ビューポート座標

getBoundingClientRect がそのまま返す値。画面上の見た目の位置を示し、スクロールすると値が変わる。

ページ座標

ビューポート座標に scrollX / scrollY を加算した値。ドキュメント先頭からの絶対位置を示し、スクロールしても変わらない。

どちらを使うかは用途によって異なります。ツールチップを画面上の決まった位置に出したいならビューポート座標が適しており、要素にマーカーを固定したいならページ座標が必要になります。

実践:ツールチップの位置決め

getBoundingClientRect の典型的な使い方として、ツールチップを要素の下に表示する例を見てみましょう。

HTML
CSS
JavaScript
<div style="padding: 40px;">
  <button id="target">ホバーしてね</button>
  <div id="tooltip" style="display:none; position:fixed; background:#333; color:#fff; padding:6px 12px; border-radius:4px; font-size:14px;">
    ツールチップです
  </div>
</div>
const target = document.getElementById('target');
const tooltip = document.getElementById('tooltip');

target.addEventListener('mouseenter', () => {
  const rect = target.getBoundingClientRect();
  tooltip.style.left = rect.left + 'px';
  tooltip.style.top = rect.bottom + 8 + 'px';
  tooltip.style.display = 'block';
});

target.addEventListener('mouseleave', () => {
  tooltip.style.display = 'none';
});

ツールチップの position を fixed にしているのがポイントです。getBoundingClientRect はビューポート座標を返すため、position: fixed な要素にそのまま left や top を設定すれば正確な位置に配置できます。position: absolute を使う場合は、親要素の位置を考慮する必要が出てくるので注意してください。

border・padding・margin との関係

getBoundingClientRect が返すサイズには border と padding が含まれますが、margin は含まれません。CSS の box-sizing の設定に関係なく、画面上で要素が実際に占めている領域を返します。

// 以下の CSS が適用された要素の場合
// width: 200px; padding: 10px; border: 2px solid; margin: 20px;

const rect = element.getBoundingClientRect();
// rect.width → 224  (200 + 10*2 + 2*2)
// margin は含まれない

この仕様は offsetWidth と一致しており、要素の視覚的な境界を示すと理解すればわかりやすいでしょう。margin は要素の外側の余白なので、要素自体のサイズには含まれないという考え方です。

注意点:リフローとパフォーマンス

getBoundingClientRect を呼び出すたびに、ブラウザはレイアウトの再計算(リフロー)を行う可能性があります。DOM を変更した直後にこのメソッドを呼ぶと、ブラウザは最新の位置情報を返すために保留中のレイアウト計算を即座に実行します。

DOM を変更する(要素の追加やスタイル変更)

getBoundingClientRect を呼び出す

ブラウザが強制リフローを実行し、値を返す

ループの中で DOM 変更と getBoundingClientRect の呼び出しを交互に行うと、毎回リフローが発生してパフォーマンスが著しく低下します。複数の要素の位置をまとめて取得したい場合は、先にすべての値を読み取ってから DOM を変更するように順序を工夫しましょう。

// 悪い例:読み取りと書き込みが交互に発生
items.forEach(item => {
  const rect = item.getBoundingClientRect(); // 読み取り
  item.style.left = rect.left + 10 + 'px';  // 書き込み → 次の読み取りでリフロー
});

// 良い例:読み取りをまとめてから書き込み
const rects = items.map(item => item.getBoundingClientRect()); // 一括読み取り
items.forEach((item, i) => {
  item.style.left = rects[i].left + 10 + 'px'; // 一括書き込み
});

読み取りと書き込みを分離するこのパターンは、DOM 操作全般におけるパフォーマンスの基本原則です。getBoundingClientRect に限らず、offsetTop や clientHeight といったレイアウト情報を返すプロパティすべてに当てはまります。

transform が適用された要素

CSS の transform が適用されている要素に対して getBoundingClientRect を呼び出すと、変換後の位置とサイズが返ります。たとえば scale(2) で拡大された要素なら、width と height は元のサイズの 2 倍の値になります。

// transform: scale(2) が適用された要素
// 元のサイズ: 100x100

const rect = element.getBoundingClientRect();
// rect.width  → 200
// rect.height → 200

これは getBoundingClientRect が「画面上で実際に見えている大きさ」を返すためです。offsetWidth や offsetHeight は transform を考慮しないため、変換後のサイズが必要なときは getBoundingClientRect を使うのが正確です。

getBoundingClientRect が返す座標の基準はどれですか?

  • ドキュメントの左上
  • ビューポートの左上
  • 親要素の左上
  • body 要素の左上
__RESULT__

getBoundingClientRect はビューポート(表示領域)の左上を基準とした座標を返します。ページ座標が必要な場合は window.scrollX / scrollY を加算します。