JavaScript | DOM 操作:要素の位置・サイズ・スクロール – offsetWidth / offsetHeight

JavaScript JavaScript
スポンサーリンク

offsetWidth / offsetHeight とは何か

offsetWidth と offsetHeight は、要素の「見た目のサイズ」をピクセルで返すプロパティです。ここが重要です:border(枠)と padding(内側余白)を含み、margin(外側余白)は含みません。スクロールバーが要素の中に出ている場合、その幅・高さも含まれます。つまり「ユーザーが見ている箱の外側サイズ」をすぐに知るための値です。


何が含まれ、何が含まれないか(箱モデルとの関係)

含まれるものと含まれないものの整理

offsetWidth/offsetHeight の計算には、content(中身)+ padding + border +(必要なら)スクロールバーが含まれます。margin は含まれません。ここが重要です:CSS の box-sizing に関係なく、この“見た目の外側寸法”で返ってくるため、レイアウトや位置合わせのときに扱いやすい数字です。

box-sizing の影響の受け方

box-sizing: content-box と border-box の違いは「CSS の width/height に何が含まれるか」ですが、offsetWidth/offsetHeight は常に“見た目の外側寸法”なので、その違いを意識せずに使えます。ここが重要です:幅合わせ・重ね合わせ・ポップアップの位置計算などでは offset の値をベースにするとズレが少なくなります。


基本の使い方(要素のサイズ取得と応用)

要素の見た目のサイズを読む

<div id="box" style="width:160px; padding:16px; border:4px solid #333">内容</div>
<script>
  console.log("offsetWidth:", box.offsetWidth);   // 160 + 16*2 + 4*2 = 200
  console.log("offsetHeight:", box.offsetHeight); // 内容の高さ + 16*2 + 4*2
</script>
HTML

ここが重要です:CSS の width(160px)に padding と border が足されて返ります。高さはテキスト量で変動しますが、同じく padding と border を含みます。

横並びの位置合わせ(右端にポップアップを付ける)

<div id="wrap" style="position:relative">
  <button id="btn" style="position:absolute; left:20px; top:20px">ボタン</button>
  <div id="popup" style="position:absolute; display:none">ポップアップ</div>
</div>
<script>
  btn.addEventListener("click", () => {
    popup.style.left = (btn.offsetLeft + btn.offsetWidth) + "px";
    popup.style.top  = btn.offsetTop + "px";
    popup.style.display = "block";
  });
</script>
HTML

ここが重要です:同じ親の中で位置合わせするなら、offsetLeft/Top と offsetWidth/Height の組み合わせが最短ルートです。右端にぴったり付けるときも計算が簡単です。

画面端のはみ出しを避ける補正

<script>
  function placeRightOf(el, menu) {
    const x = el.offsetLeft + el.offsetWidth;
    const y = el.offsetTop;
    const vw = el.offsetParent.clientWidth;
    const mh = menu.offsetHeight;
    const mw = menu.offsetWidth;
    const nx = Math.min(x, vw - mw - 8); // 右端から8px残す
    const ny = Math.max(0, Math.min(y, el.offsetParent.clientHeight - mh - 8));
    menu.style.left = nx + "px";
    menu.style.top  = ny + "px";
  }
</script>
HTML

ここが重要です:親の見えている領域(clientWidth/clientHeight)とメニューの offsetWidth/Height を使って“はみ出し防止”ができます。


似た値との違い(clientWidth/Height・getBoundingClientRect)

clientWidth / clientHeight との違い

clientWidth/Height は「content+padding」を返し、border とスクロールバーは含みません。ここが重要です:より“内側の空間”が欲しいなら client を、外側の箱寸法が欲しいなら offset を使います。スクロール可能な要素でスクロールバーのスペースを考慮したい場合は offset が適任です。

getBoundingClientRect との違い

getBoundingClientRect() は画面(ビューポート)に対する位置とサイズを返します。width/height はサブピクセル(小数)になることがあり、スクロールやズームも反映されます。offsetWidth/Height は整数ピクセルで返されます。ここが重要です:画面座標での正確な重なり判定や交差判定には rect、レイアウト用の“ざっくり箱サイズ”には offset が向いています。


スクロールバー・ボーダー・表示状態の影響

スクロールバーを含むかどうか

overflow: auto などで要素内にスクロールバーが表示されていれば、その幅(一般に縦スクロールバーは幅、横スクロールバーは高さ)を offsetWidth/Height は含みます。ここが重要です:内側にコンテンツをぴったり並べたいとき、スクロールバー分の余白も考慮するなら offset を基準にすると安全です。

border がある親子レイアウト

border は offset の箱に含まれます。そのため、border の有無で“置ける位置”が変わります。ここが重要です:細かなピクセル合わせが必要なら、border/padding の有無を明示し、基準となる親の CSS を安定化しておきましょう。

非表示要素の注意点

display: none の要素は offsetWidth/Height が 0 になります。一時的に計測したい場合は、visibility: hidden(表示はしないがレイアウトに参加)で計測し、必要な値が得られたら元に戻す方法があります。ここが重要です:開閉式 UI の事前計測は表示状態が鍵です。


実践例(レスポンシブ計測、折りたたみアニメ、等分レイアウト)

レスポンシブで“収まるか”判定して省略記号に切り替える

<div id="title" style="max-width:240px; padding:8px; border:1px solid #ccc; white-space:nowrap; overflow:hidden; text-overflow:ellipsis"></div>
<script>
  title.textContent = "とても長いタイトルが入ることを想定しています";
  function fits() {
    return title.scrollWidth <= title.offsetWidth; // 収まっているか
  }
  console.log("収まる?", fits());
</script>
HTML

ここが重要です:scrollWidth(実際の必要幅)と offsetWidth(見た目の箱幅)を比較すれば、折り返しや省略の判定が簡単です。

折りたたみアニメの自然な高さ取得

<div id="panel" style="overflow:hidden; transition: height .25s ease"></div>
<script>
  function openPanel(content) {
    panel.textContent = content;
    panel.style.height = "auto"; // 自然高さに
    const h = panel.offsetHeight; // 見た目の高さを取得
    panel.style.height = "0px";
    requestAnimationFrame(() => panel.style.height = h + "px");
  }
  function closePanel() {
    panel.style.height = "0px";
  }
  openPanel("テキスト\nテキスト\nテキスト");
</script>
HTML

ここが重要です:コンテンツの自然な高さを offsetHeight で計測してからアニメに使うと、コンテンツ量に応じてスムーズに開閉できます。

等分レイアウトで“余り幅”を正確に配分

<div id="wrap" style="display:flex; gap:8px">
  <div class="box" style="flex:1; padding:8px; border:1px solid #ccc">A</div>
  <div class="box" style="flex:1; padding:8px; border:1px solid #ccc">B</div>
  <div class="box" style="flex:1; padding:8px; border:1px solid #ccc">C</div>
</div>
<script>
  function leftover() {
    const used = Array.from(wrap.children).reduce((s, el) => s + el.offsetWidth, 0);
    const total = wrap.clientWidth;
    return total - used; // gapやborder込みの使用幅を差し引いた余り
  }
  console.log("余り幅:", leftover());
</script>
HTML

ここが重要です:見た目に使っている幅を offsetWidth で合計すると、残余を正しく計算できます。微調整や最終アイテムの拡張に使えます。


よくある落とし穴と回避策

小数のズレに悩む

offsetWidth/Height は整数で、サブピクセルは切り捨てられます。ズームやスケーリングで正確な交差や重なり判定が必要なら、getBoundingClientRect の width/height(小数)を使いましょう。ここが重要です:粗い箱寸法なら offset、精密な画面寸法なら rect を選ぶのがコツです。

スクロールバーで期待値と違う

スクロールバーを含むため、内側の“実際に使える幅”は clientWidth/Height のほうが正確です。ここが重要です:内側レイアウトの計算では client、外側配置の計算では offset を使い分けます。

非表示状態で計測してしまう

display: none の要素は 0 を返します。開閉 UI の計測は、一時的に表示(または visibility: hidden)状態で行いましょう。ここが重要です:“見えている箱のサイズ”という定義に沿った計測が必要です。

位置とサイズをごちゃ混ぜに計算する

位置は offsetLeft/Top(または rect.left/top)、サイズは offsetWidth/Height(または rect.width/height)と、役割を分けて使います。ここが重要です:座標と寸法を混同するとズレの原因になります。基準(親か画面か)も常に意識しましょう。


まとめ

offsetWidth/offsetHeight は「要素の見た目の外側サイズ」を、padding・border・(必要なら)スクロールバー込みの整数ピクセルで返します。外側の配置や位置合わせ、はみ出し補正、自然高さの取得に向いており、内側のレイアウト計算なら clientWidth/Height、画面座標の精密計測なら getBoundingClientRect を使い分けます。表示状態・スクロールバー・box-model を意識して設計すれば、初心者でもズレの少ないレイアウトと気持ちよい UI を実装できます。

タイトルとURLをコピーしました