2022年6月26日

要素のサイズとスクロール

要素の幅、高さ、その他の幾何学的特徴に関する情報を読み取るための多くのJavaScriptプロパティがあります。

JavaScriptで要素を移動したり配置したりするときによく必要になります。

サンプル要素

プロパティを実証するためのサンプル要素として、以下に示すものを使用します。

<div id="example">
  ...Text...
</div>
<style>
  #example {
    width: 300px;
    height: 200px;
    border: 25px solid #E8C48F;
    padding: 20px;
    overflow: auto;
  }
</style>

ボーダー、パディング、およびスクロールがあります。全機能セットです。マージンは要素自体の一部ではないため、マージンはなく、マージン用の特別なプロパティもありません。

要素は次のようになります。

サンドボックスでドキュメントを開くことができます。

スクロールバーに注意してください

上の図は、要素にスクロールバーがある場合の最も複雑なケースを示しています。一部のブラウザ(すべてではない)は、コンテンツ(上の「コンテンツ幅」とラベル付けされている)から取得して、スクロールバーのスペースを予約します。

したがって、スクロールバーがない場合、コンテンツ幅は300pxになりますが、スクロールバーが16px幅(幅はデバイスやブラウザによって異なる場合があります)の場合、300 - 16 = 284pxのみが残り、それを考慮する必要があります。それが、この章の例でスクロールバーがあることを前提としている理由です。スクロールバーがない場合、一部の計算は簡単です。

padding-bottom領域はテキストで埋められる場合があります

通常、パディングは図では空で表示されますが、要素に多くのテキストがあり、オーバーフローする場合は、ブラウザはpadding-bottomに「オーバーフロー」テキストを表示します。これは正常です。

幾何学

幾何学プロパティを含む全体像を次に示します。

これらのプロパティの値は技術的には数値ですが、これらの数値は「ピクセル単位」であるため、ピクセル測定値です。

要素の外側から始めて、プロパティを調べていきましょう。

offsetParent、offsetLeft/Top

これらのプロパティはめったに必要ありませんが、それでも「最も外側の」幾何学プロパティであるため、それらから始めます。

offsetParentは、ブラウザがレンダリング中に座標を計算するために使用する最も近い祖先です。

これは、次のいずれかに該当する最も近い祖先です。

  1. CSSで配置されたもの(positionabsoluterelativefixed、またはsticky)、または
  2. <td><th>、または<table>、または
  3. <body>.

プロパティoffsetLeft/offsetTopは、offsetParentの左上隅を基準としたx/y座標を提供します。

以下の例では、内側の<div>にはoffsetParentとして<main>があり、offsetLeft/offsetTopが左上隅(180)からシフトしています。

<main style="position: relative" id="main">
  <article>
    <div id="example" style="position: absolute; left: 180px; top: 180px">...</div>
  </article>
</main>
<script>
  alert(example.offsetParent.id); // main
  alert(example.offsetLeft); // 180 (note: a number, not a string "180px")
  alert(example.offsetTop); // 180
</script>

offsetParentnullになるいくつかのケースがあります。

  1. 表示されない要素(display:noneまたはドキュメント内にない)。
  2. <body>および<html>の場合。
  3. position:fixedの要素の場合。

offsetWidth/Height

次に、要素自体に進みましょう。

これらの2つのプロパティは最も単純なものです。これらは、要素の「外側」の幅/高さを提供します。言い換えれば、ボーダーを含むフルサイズです。

サンプル要素の場合

  • offsetWidth = 390 – 外側の幅。内側のCSS幅(300px)にパディング(2 * 20px)とボーダー(2 * 25px)を加えたものとして計算できます。
  • offsetHeight = 290 – 外側の高さ。
幾何学プロパティは、表示されていない要素の場合、ゼロ/ヌルになります

幾何学プロパティは、表示された要素に対してのみ計算されます。

要素(またはその祖先のいずれか)にdisplay:noneがある場合、またはドキュメント内にない場合、すべての幾何学プロパティはゼロになります(またはoffsetParentの場合はnull)。

たとえば、要素を作成したが、まだドキュメントに挿入していない場合、または要素(またはその祖先)にdisplay:noneがある場合、offsetParentnullになり、offsetWidthoffsetHeight0になります。

これを使用して、要素が非表示になっているかどうかを次のように確認できます。

function isHidden(elem) {
  return !elem.offsetWidth && !elem.offsetHeight;
}

このようなisHiddenは、画面上にあり、サイズがゼロの要素に対してtrueを返すことに注意してください。

clientTop/Left

要素の内部にはボーダーがあります。

それらを測定するために、プロパティclientTopclientLeftがあります。

この例では

  • clientLeft = 25 – 左ボーダーの幅
  • clientTop = 25 – 上ボーダーの幅

...ただし、正確に言うと、これらのプロパティはボーダーの幅/高さではなく、外側から見た内側の相対座標です。

違いは何ですか?

ドキュメントが右から左の場合(オペレーティングシステムがアラビア語またはヘブライ語の場合)に表示されます。スクロールバーは右側ではなく左側にあり、clientLeftにはスクロールバーの幅も含まれます。

その場合、clientLeft25ではなく、スクロールバーの幅25 + 16 = 41になります。

ヘブライ語での例を次に示します

clientWidth/Height

これらのプロパティは、要素のボーダーの内側の領域のサイズを提供します。

これらには、スクロールバーなしでコンテンツ幅とパディングが含まれます。

上の図では、まずclientHeightを考えてみましょう。

水平スクロールバーはないため、ボーダーの内側の合計に完全に一致します。CSSの高さ200pxに上下のパディング(2 * 20px)を加えた合計240pxです。

次に、clientWidthです。コンテンツ幅は300pxではなく284pxです。これは、16pxがスクロールバーによって占有されているためです。したがって、合計は284pxに左右のパディングを加えた合計324pxです。

パディングがない場合、clientWidth/Heightは、ボーダーとスクロールバー(ある場合)の内側のコンテンツ領域に正確に一致します。

したがって、パディングがない場合、clientWidth/clientHeightを使用してコンテンツ領域のサイズを取得できます。

scrollWidth/Height

これらのプロパティはclientWidth/clientHeightに似ていますが、スクロールアウト(非表示)部分も含まれています

上の図では

  • scrollHeight = 723 – スクロールアウト部分を含む、コンテンツ領域の完全な内側の高さです。
  • scrollWidth = 324 – 完全な内側の幅です。ここでは水平スクロールがないため、clientWidthと同じです。

これらのプロパティを使用して、要素をその全幅/高さに拡張できます。

このように

// expand the element to the full content height
element.style.height = `${element.scrollHeight}px`;

ボタンをクリックして要素を拡張します

テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト テキスト

scrollLeft/scrollTop

プロパティscrollLeft/scrollTopは、要素の非表示のスクロールアウト部分の幅/高さです。

下の図では、垂直スクロールのあるブロックのscrollHeightscrollTopを確認できます。

言い換えれば、scrollTopは「どれだけ上にスクロールされたか」です。

scrollLeft/scrollTopは変更可能

ここにあるほとんどの幾何学プロパティは読み取り専用ですが、scrollLeft/scrollTopは変更でき、ブラウザは要素をスクロールします。

下の要素をクリックすると、コードelem.scrollTop += 10が実行されます。これにより、要素のコンテンツが10px下にスクロールされます。

クリック

1
2
3
4
5
6
7
8
9

scrollTop0または1e9のような大きな値に設定すると、要素がそれぞれ一番上/一番下までスクロールされます。

CSSから幅/高さを取得しないでください

DOM要素の幾何学プロパティについて説明しました。これを使用して、幅、高さ、距離を計算できます。

しかし、スタイルとクラスの章で知っているように、getComputedStyleを使用してCSSの高さと幅を読み取ることができます。

では、次のように、getComputedStyleで要素の幅を読み取らないのはなぜですか?

let elem = document.body;

alert( getComputedStyle(elem).width ); // show CSS width for elem

代わりに幾何学プロパティを使用する必要があるのはなぜですか?理由は2つあります

  1. まず、CSSのwidth/heightは別のプロパティbox-sizingに依存しています。これは、CSSの幅と高さが「何であるか」を定義します。CSSの目的でのbox-sizingの変更により、このようなJavaScriptが壊れる可能性があります。

  2. 次に、CSSのwidth/heightは、たとえばインライン要素の場合、autoになる可能性があります。

    <span id="elem">Hello!</span>
    
    <script>
      alert( getComputedStyle(elem).width ); // auto
    </script>

    CSSの観点から見ると、width:autoは完全に正常ですが、JavaScriptでは、計算で使用できるpx単位の正確なサイズが必要です。したがって、ここでCSSの幅は役に立ちません。

さらに、もう1つ理由があります。スクロールバーです。スクロールバーなしでは正常に機能するコードが、スクロールバーがあるとバグになる場合があります。これは、一部のブラウザではスクロールバーがコンテンツからスペースを占有するためです。したがって、コンテンツで使用できる実際の幅は、CSS幅よりも小さくなります。また、clientWidth/clientHeightはそれを考慮に入れています。

...しかし、getComputedStyle(elem).widthを使用すると、状況は異なります。一部のブラウザ(例:Chrome)は、スクロールバーを引いた実際の内部幅を返し、一部のブラウザ(例:Firefox)は、CSSの幅(スクロールバーを無視)を返します。このようなクロスブラウザの違いが、getComputedStyleを使用せず、幾何学プロパティに頼る理由です。

もしブラウザがスクロールバーのための領域を確保する場合(Windowsのほとんどのブラウザがそうです)、以下でテストできます。

テキストを持つ要素は、CSSでwidth:300pxが指定されています。

デスクトップのWindows OSでは、Firefox、Chrome、Edgeのすべてがスクロールバーのために領域を確保します。しかし、Firefoxは300pxと表示する一方で、ChromeとEdgeはそれよりも小さい値を表示します。これは、FirefoxがCSSの幅を返すのに対し、他のブラウザは「実際の」幅を返すためです。

この違いは、JavaScriptからgetComputedStyle(...).widthを読み取る場合にのみ発生することに注意してください。見た目ではすべて正常です。

まとめ

要素は以下の幾何学的プロパティを持ちます。

  • offsetParent – 最も近い位置指定された祖先要素、またはtdthtablebodyです。
  • offsetLeft/offsetTopoffsetParentの左上端からの相対座標です。
  • offsetWidth/offsetHeight – ボーダーを含む要素の「外側」の幅/高さです。
  • clientLeft/clientTop – 左上外側の角から左上内側(コンテンツ+パディング)の角までの距離です。左から右のOSの場合、常に左/上のボーダーの幅です。右から左のOSの場合、垂直スクロールバーが左側にあるため、clientLeftにはその幅も含まれます。
  • clientWidth/clientHeight – スクロールバーを含まない、パディングを含むコンテンツの幅/高さです。
  • scrollWidth/scrollHeightclientWidth/clientHeightと同様にコンテンツの幅/高さですが、スクロールアウトされた、見えない部分も含みます。
  • scrollLeft/scrollTop – 要素の左上角から始まる、スクロールアウトされた上部の幅/高さです。

すべてのプロパティは読み取り専用ですが、scrollLeft/scrollTopは変更されるとブラウザが要素をスクロールさせます。

タスク

重要度: 5

elem.scrollTopプロパティは、上からスクロールアウトされた部分のサイズです。下部のスクロール量(scrollBottomと呼びましょう)を取得するにはどうすればよいでしょうか?

任意のelemで動作するコードを書いてください。

追伸: スクロールがない場合や、要素が完全に下にスクロールされている場合は、0を返すようにコードを確認してください。

解答は次のとおりです。

let scrollBottom = elem.scrollHeight - elem.scrollTop - elem.clientHeight;

言い換えれば、(全高)マイナス(スクロールアウトされた上部)マイナス(可視部分) – これがまさにスクロールアウトされた下部です。

重要度: 3

標準のスクロールバーの幅を返すコードを書いてください。

Windowsでは通常12pxから20pxの間で変動します。ブラウザがスクロールバーのために領域を確保しない場合(スクロールバーがテキストの上に半透明で表示される場合もあります)、0pxになることがあります。

追伸: コードは任意のHTMLドキュメントで動作する必要があり、その内容に依存してはいけません。

スクロールバーの幅を取得するには、スクロールがあり、ボーダーとパディングのない要素を作成できます。

そうすると、その全幅offsetWidthと内側のコンテンツ領域の幅clientWidthの差が、まさにスクロールバーになります。

// create a div with the scroll
let div = document.createElement('div');

div.style.overflowY = 'scroll';
div.style.width = '50px';
div.style.height = '50px';

// must put it in the document, otherwise sizes will be 0
document.body.append(div);
let scrollWidth = div.offsetWidth - div.clientWidth;

div.remove();

alert(scrollWidth);
重要度: 5

ソースドキュメントは次のようになっています。

フィールドの中心の座標は何ですか?

それらを計算し、緑色のフィールドの中心にボールを配置するために使用してください。

  • 要素はCSSではなく、JavaScriptで移動する必要があります。
  • コードは、任意のボールサイズ(102030ピクセル)と任意のフィールドサイズで動作し、指定された値に縛られないようにする必要があります。

追伸:もちろん、センタリングはCSSで行うことができますが、ここではまさにJavaScriptで行いたいのです。今後、JavaScriptを使用する必要がある、より複雑な状況に遭遇するでしょう。ここでは「準備運動」をします。

タスクのサンドボックスを開きます。

ボールはposition:absoluteを持っています。これは、そのleft/top座標が、最も近い位置指定された要素、つまり#fieldposition:relativeを持つため)から測定されることを意味します。

座標はフィールドの内側の左上隅から始まります。

内側のフィールドの幅/高さはclientWidth/clientHeightです。したがって、フィールドの中心の座標は(clientWidth/2, clientHeight/2)です。

...しかし、ball.style.left/topをそのような値に設定すると、ボール全体ではなく、ボールの左上端が中心に配置されます。

ball.style.left = Math.round(field.clientWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2) + 'px';

これはどのように見えるかを示しています。

ボールの中心をフィールドの中心に合わせるには、ボールをその幅の半分だけ左に、高さの半分だけ上に移動する必要があります。

ball.style.left = Math.round(field.clientWidth / 2 - ball.offsetWidth / 2) + 'px';
ball.style.top = Math.round(field.clientHeight / 2 - ball.offsetHeight / 2) + 'px';

これでボールが最終的に中央に配置されました。

注意:落とし穴!

<img>に幅/高さがない場合、コードは確実に動作しません。

<img src="ball.png" id="ball">

ブラウザが画像(タグ属性またはCSSから)の幅/高さを認識していない場合、画像が読み込まれるまでそれらを0とみなします。

したがって、画像が読み込まれるまでball.offsetWidthの値は0になります。これにより、上記のコードで間違った座標が生じます。

最初の読み込み後、ブラウザは通常画像をキャッシュし、再読み込み時にすぐにサイズを取得します。しかし、最初の読み込みではball.offsetWidthの値は0です。

<img>width/heightを追加することで、それを修正する必要があります。

<img src="ball.png" width="40" height="40" id="ball">

...またはCSSでサイズを提供します。

#ball {
  width: 40px;
  height: 40px;
}

サンドボックスで解決策を開きます。

重要度: 5

getComputedStyle(elem).widthelem.clientWidthの違いは何ですか?

少なくとも3つの違いを挙げてください。多ければ多いほど良いです。

違い

  1. clientWidthは数値ですが、getComputedStyle(elem).widthは末尾にpxが付いた文字列を返します。
  2. getComputedStyleは、インライン要素の場合、"auto"のような数値以外の幅を返すことがあります。
  3. clientWidthは、要素の内部コンテンツ領域にパディングを加えたものですが、CSSの幅(標準のbox-sizingを使用した場合)は、内部コンテンツ領域でパディングを含まないものです。
  4. スクロールバーがあり、ブラウザがそのために領域を確保する場合、一部のブラウザはCSSの幅からその領域を差し引き(コンテンツでは使用できなくなるため)、一部は差し引きません。clientWidthプロパティは常に同じです。スクロールバーのサイズは、確保されている場合は差し引かれます。
チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善するための提案がある場合は、コメントするのではなく、GitHub issueを送信するか、プルリクエストを送信してください。
  • 記事で何か理解できないことがある場合は、詳しく説明してください。
  • コードを数語挿入するには<code>タグを使用し、複数行の場合は<pre>タグで囲み、10行を超える場合はサンドボックスを使用してください (plnkr, jsbin, codepen…)。