【JavaScript】HTML 要素のリサイズを検知する Resize Observer

ウェブブラウザーのウィンドウをリサイズするなどして、コンテンツのサイズが変わることがあります。そのリサイズに合わせて何かを実行したいというニーズは多いのではないでしょうか。今回は、そのニーズに応える Resize Observer について解説します。

Resize Observer とは

まずは、Resize Observer の簡単なサンプルコードをご覧ください。ブラウザーのウィンドウの幅を動かすと div 要素の横幅も変わります。それに合わせて、div 要素の背景色が変化しますが、その色は div 要素の幅をもとに算出されています。

<div id="sample1"></div>
const resizeObserver = new ResizeObserver((entries) => {
  const w = entries[0].contentRect.width;
  const hue = w % 360;
  entries[0].target.style.backgroundColor = `hsl(${hue}, 100%, 50%)`;
});
resizeObserver.observe(document.querySelector('#sample1'));

ブラウザーウィンドウの横幅を縮めてみてください。

このサンプルは、window オブジェクトで発生する resize イベントを使っても実現することはできます。わざわざ Resize Observer を使って実現する必要性はありません。

const div = document.querySelector('#sample1');
window.addEventListener('resize', () => {
  const w = div.offsetWidth;
  const hue = w % 360;
  div.style.backgroundColor = `hsl(${hue}, 100%, 50%)`;
});

では、次のサンプルはどうでしょう。次のサンプルは div 要素を textarea 要素に変えただけです。

<textarea id="sample2"></textarea>
const resizeObserver = new ResizeObserver((entries) => {
  const w = entries[0].contentRect.width;
  const hue = w % 360;
  entries[0].target.style.backgroundColor = `hsl(${hue}, 100%, 50%)`;
});
resizeObserver.observe(document.querySelector('#sample2'));

このサンプルではブラウザーのウィンドウをリサイズしても textarea 要素はリサイズされません。さらにブラウザーのウィンドウをリサイズしなくても textarea 要素のサイズを変更することができてしまいます。こういったケースでは window オブジェクトの resize イベントを使って実現できませんので、Resize Observer が必要になります。

Resize Observer の検知トリガー

前述の例では Resize Observer で HTML 要素のリサイズを検知しましたが、Resize Observer はそのほか次のような変化を検知します。

  • 要素が DOM に挿入または削除されたとき
  • CSS の display プロパティが none になったとき、または、none でなくなったとき

一方で、次の要素はリサイズを検知できません。

  • CSS の display プロパティが inline の要素
  • 要素のサイズが 0 x 0 の要素

Resize Observer の開始

では、Resize Observer のコードの書き方を解説します。Resize Observer を利用するためには、まず ResizeObserver オブジェクトを生成します。ResizeObserver オブジェクト生成の際には、イベントが発生したときに呼び出されるコールバック関数を引数に指定します。

const resizeObserver = new ResizeObserver((entries) => {
  console.log('イベントを検知しました。');
});

このコールバック関数の詳細は後述します。次に、リサイズを検知したい HTML 要素を Resize Observer に登録します。登録には ResizeObserver オブジェクト (変数 resizeObserver) の observe() メソッドを使います。この observe() メソッドには HTML 要素の DOM オブジェクトを指定します。

resizeObserver.observe(document.querySelector('#sample1'));

これで、該当の HTML 要素でリサイズが発生すると、ResizeObserver オブジェクトに登録されたコールバック関数が呼び出されます。

同じ ResizeObserver オブジェクト (変数 resizeObserver) に複数の HTML 要素を検知対象にすることも可能です。

resizeObserver.observe(document.querySelector('#sample1'));
resizeObserver.observe(document.querySelector('#sample2'));

この場合、どちらの HTML 要素のリサイズが発生しても、前述のコールバック関数が呼び出されます。つまり、複数の HTML 要素のリサイズのイベントが一つのコールバック関数に集約されて呼び出されることになります。

コールバック関数の実行

コールバック関数の第一引数には、リサイズのイベントの情報が格納された ResizeObserverEntry オブジェクト (変数 entries) の配列が引き渡されます。また、第二引数には親となる ResizeObserver オブジェクト (変数 observer) が引き渡されます。下記サンプルコードでは、コールバック関数の第二引数の変数 observer は、変数 resizeObserver と同じオブジェクトです。

const resizeObserver = new ResizeObserver((entries, observer) => {
  console.log('イベントを検知しました。');
});

第二引数はあまり使う機会がないかもしれませんが、このコールバック関数で重要なのは、第一引数に引き渡される ResizeObserverEntry オブジェクトの配列 (変数 entries) です。

では、 ResizeObserverEntry オブジェクトの詳細を見てみましょう。 ResizeObserverEntry オブジェクトには、次のプロパティがセットされています。

プロパティ説明
targetリサイズが発生した HTML 要素の DOM オブジェクト
contentRectリサイズが発生した HTML 要素のコンテントボックスサイズ情報を格納したオブジェクト
borderBoxSizeリサイズが発生した HTML 要素のボーダーボックスサイズ情報を格納したオブジェクトのリスト
contentBoxSizeリサイズが発生した HTML 要素のコンテントボックスサイズ情報を格納したオブジェクトのリスト
devicePixelContentBoxSizeリサイズが発生した HTML 要素のコンテントボックスサイズ情報を格納したオブジェクトのリスト

contentRect は次のようなデータを格納しています。

const resizeObserver = new ResizeObserver((entries) => {
  console.log(entries[0].contentRect);
});

このように、該当の HTML 要素の矩形情報が格納されていますが、とりわけ widthheight はコンテントボックスサイズに基づいた値になります。コンテントボックスサイズとは、ボーダー、パディングを含めないコンテンツ部分のみの矩形領域を表します。ボックスサイズに関しては「HTML 要素の寸法の取得方法まとめ」をご覧ください。

borderBoxSize, contentBoxSize, devicePixelContentBoxSize は、前述の contentRect と同様にリサイズが発生した HTML 要素の矩形情報を格納しているのですが、リストである点が異なります。実際には FrozenArray という型のオブジェクトで、読み取り専用の Array オブジェクトです。

これらプロパティがリストである理由は、将来的に CSS の段組みレイアウトのように 1 つの矩形で表せない HTML 要素を扱う場合を想定しています。現状 Resize Observer はそういったマルチカラムをサポートしていないため、FrozenArray には 1 つのオブジェクトしか含まれません。

それぞれのプロパティは次のようなデータを格納しています。

const resizeObserver = new ResizeObserver((entries) => {
  console.log(entries[0].borderBoxSize);
  console.log(entries[0].contentBoxSize);
  console.log(entries[0].devicePixelContentBoxSize);
});

borderBoxSize, contentBoxSize, devicePixelContentBoxSize の配列に含まれるオブジェクトには、inlineSize プロパティと blockSize プロパティがあります。これは該当の HTML 要素の幅と高さです。ただし、borderBoxSize は HTML 要素のパディングとボーダーを含めた幅と高さを表してます。このサイズは、要素オブジェクトの offsetWidth, offsetHeight プロパティと同じです。または、getBoundingClientRect() メソッドから得られる width, height プロパティと同じです。

contentBoxSizedevicePixelContentBoxSize はいずれもパティングやボーダーを除いたコンテンツ部分の幅と高さを表しますが、その違いは CSS ピクセルかデバイスピクセルの違いです。通常のデスクトップ PC のモニターでは違いが出ませんが、スマートフォンではそれぞれの値が違います。

次のコードでその違いが分かります。

<div id="box">
  <dl>
    <dt>CSS ピクセル</dt>
    <dd><span id="cssw">-</span> x <span id="cssh">-</span></dd>
    <dt>デバイス ピクセル</dt>
    <dd><span id="devw">-</span> x <span id="devh">-</span></dd>
  </dl>
</div>
const resizeObserver = new ResizeObserver((entries) => {
  document.getElementById('cssw').textContent =
    entries[0].contentBoxSize[0].inlineSize;
  document.getElementById('cssh').textContent =
    entries[0].contentBoxSize[0].blockSize;
  document.getElementById('devw').textContent =
    entries[0].devicePixelContentBoxSize[0].inlineSize;
  document.getElementById('devh').textContent =
    entries[0].devicePixelContentBoxSize[0].blockSize;
});
resizeObserver.observe(document.querySelector('#box'));
CSS ピクセル
x
デバイス ピクセル
x

なお、2022 年5 月現在、Safari は devicePixelContentBoxSize をサポートしていません。そのため、上記コードは iPhone では動作しません。

Resize Observer の停止

一度登録した Resize Observer を停止するには、2 つの方法があります。一つは登録時にセットした HTML 要素の DOM オブジェクトを指定して停止する unobserve() メソッドを使う方法です。もう一つはすべての登録を一括で解除する disconnect() メソッドを使う方法です。いずれのメソッドも ResizeObserver オブジェクトのメソッドです。

次のサンプルは、div 要素に Resize Observer をセットし、10 回のリサイズを検知したのちに Resize Observer を個別に解除します。

let count = 0;
const resizeObserver = new ResizeObserver((entries, observer) => {
  count ++;
  if(count === 10) {
    observer.unobserve(entries[0].target);
    console.log('Resize Observer を解除しました。');
  }
});
resizeObserver.observe(document.querySelector('#box'));

次の例は 2 つの div 要素に Resize Observer をセットし、10 回のリサイズを検知したのちに Resize Observer をまとめて解除します。

let count = 0;
const resizeObserver = new ResizeObserver((entries, observer) => {
  count ++;
  if(count === 10) {
    observer.disconnect();
    console.log('Resize Observer を解除しました。');
  }
});
resizeObserver.observe(document.querySelector('#box1'));
resizeObserver.observe(document.querySelector('#box2'));

まとめ

近年のウェブサイトやウェブアプリケーションはレスポンシブウェブデザインとなっていることがほとんどでしょう。ページ上のさまざまなコンテンツのサイズは、ブラウザーウィンドウサイズやビューポートに応じて変化します。さらに、ユーザー操作によってコンテンツを動かすといったことも想定されます。そのような状況の中、個々の HTML 要素のリサイズの検知のニーズは高まっていくのではないでしょうか。

今回は以上で終わりです。最後まで読んでくださりありがとうございました。それでは次回の記事までごきげんよう。

Share