ResizeObserver: 要素の document.onresize に似ています。

ResizeObserver は、要素のサイズが変更されたときに通知します。

ResizeObserver 以前は、ドキュメントの resize イベントにリスナーをアタッチして、ビューポートの寸法が変更されたときに通知を受け取る必要がありました。次に、イベント ハンドラで、その変更の影響を受けた要素を特定し、特定のルーティンを呼び出して適切に対応する必要があります。サイズ変更後に要素の新しいディメンションが必要な場合は、getBoundingClientRect() または getComputedStyle() を呼び出す必要がありました。読み取りと書き込みのすべてをバッチ処理しないと、レイアウト スラッシングが発生する可能性があります。すべて

これは、メイン ウィンドウのサイズを変更せずに要素のサイズを変更するケースさえもカバーしていませんでした。たとえば、新しい子の追加や、要素の display スタイルを none に設定するなどのアクションによって、要素や兄弟要素、またはその祖先のサイズを変更できます。

これが ResizeObserver が便利なプリミティブである理由です。変更の原因に関係なく、監視対象の要素のサイズの変更に反応します。また、監視対象の要素の新しいサイズにもアクセスできます。

対応ブラウザ

  • Chrome: 64.
  • Edge: 79.
  • Firefox: 69。
  • Safari: 13.1。

ソース

API

上記の Observer 接尾辞を持つすべての API は、単純な API 設計を共有しています。ResizeObserver も例外ではありません。ResizeObserver オブジェクトを作成し、コールバックをコンストラクタに渡します。コールバックには、要素の新しいディメンションを含む ResizeObserverEntry オブジェクトの配列(観測された要素ごとに 1 つのエントリ)が渡されます。

var ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    const cr = entry.contentRect;

    console.log('Element:', entry.target);
    console.log(`Element size: ${cr.width}px x ${cr.height}px`);
    console.log(`Element padding: ${cr.top}px ; ${cr.left}px`);
  }
});

// Observe one or multiple elements
ro.observe(someElement);

詳細

レポートの内容

通常、ResizeObserverEntrycontentRect というプロパティを介して要素のコンテンツ ボックスを報告します。このプロパティは DOMRectReadOnly オブジェクトを返します。コンテンツ ボックスは、コンテンツを配置できるボックスです。境界ボックスからパディングを差し引いたものです。

CSS ボックスモデルの図。

ResizeObservercontentRect のディメンションとパディングの両方をレポートしますが、contentRect のみを監視します。contentRect を要素の境界ボックスと混同しないでくださいgetBoundingClientRect() によって報告される境界ボックスは、要素全体とその子孫を含むボックスです。SVG はこのルールの例外であり、ResizeObserver は境界ボックスのディメンションをレポートします。

Chrome 84 では、ResizeObserverEntry に 3 つの新しいプロパティが追加され、より詳細な情報を確認できるようになりました。これらのプロパティはそれぞれ、blockSize プロパティと inlineSize プロパティを含む ResizeObserverSize オブジェクトを返します。この情報は、コールバックが呼び出された時点でオブザーバーされた要素に関するものです。

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

これらのアイテムはすべて読み取り専用の配列を返します。これは、将来的に、複数の列のシナリオで発生する複数のフラグメントを持つ要素をサポートできるようになることを期待しているためです。現時点では、これらの配列には 1 つの要素のみが含まれます。

以下のプロパティはプラットフォームでサポートされる範囲は限られていますが、最初の 2 つのプロパティはすでに Firefox でサポートされています

いつ報告されましたか?

仕様では、ResizeObserver はペイントの前に、およびレイアウトの後にすべてのサイズ変更イベントを処理することが規定されています。そのため、ResizeObserver のコールバックは、ページのレイアウトを変更するのに最適な場所です。ResizeObserver の処理はレイアウトとペイントの間に行われるため、レイアウトのみが無効になり、ペイントは無効になりません。

承知しました

コールバック内で監視対象要素のサイズを ResizeObserver に変更するとどうなりますか?答えは、コールバックへの別の呼び出しがすぐにトリガーされることです。幸い、ResizeObserver には、無限のコールバック ループと循環依存関係を回避するメカニズムがあります。変更が同じフレームで処理されるのは、サイズ変更された要素が、前のコールバックで処理された最も浅い要素よりも DOM ツリー内で深い場合のみです。処理されなかった場合は、次のフレームに延期されます。

アプリケーション

ResizeObserver を使用すると、要素ごとのメディアクエリを実装できます。要素を監視することで、設計のブレークポイントを強制的に定義し、要素のスタイルを変更できます。次のでは、2 番目のボックスの境界半径は幅に応じて変更されます。

const ro = new ResizeObserver(entries => {
  for (let entry of entries) {
    entry.target.style.borderRadius =
        Math.max(0, 250 - entry.contentRect.width) + 'px';
  }
});
// Only observe the second box
ro.observe(document.querySelector('.box:nth-child(2)'));

チャット ウィンドウも興味深い例です。一般的な上から下への会話レイアウトで発生する問題は、スクロールの位置です。ユーザーが混乱しないようにするには、ウィンドウを会話の一番下に固定し、最新のメッセージが表示されるようにします。また、どのようなレイアウト変更でも(スマートフォンを横向きから縦向きに切り替える、またはその逆)、同じ結果が得られる必要があります。

ResizeObserver を使用すると、両方のシナリオに対応する単一のコードを書くことができます。ウィンドウのサイズ変更は、ResizeObserver が定義上キャプチャできるイベントですが、appendChild() を呼び出すと(overflow: hidden が設定されていない場合)新しい要素用のスペースが必要になるため、その要素のサイズ変更も行われます。このため、わずか数行で目的の効果を実現できます。

const ro = new ResizeObserver(entries => {
  document.scrollingElement.scrollTop =
    document.scrollingElement.scrollHeight;
});

// Observe the scrollingElement for when the window gets resized
ro.observe(document.scrollingElement);

// Observe the timeline to process new messages
ro.observe(timeline);

便利だと思いませんか?

ここから、ユーザーが手動で上にスクロールし、新しいメッセージが届いたときにスクロールでそのメッセージを維持する場合に対処するためのコードを追加できます。

別のユースケースは、独自のレイアウトを実行するあらゆる種類のカスタム要素です。ResizeObserver までは、子要素を再レイアウトできるように、サイズが変更されたときに通知を受け取る信頼できる方法がありませんでした。

Interaction to Next Paint(INP)への影響

Interaction to Next Paint(INP)は、ユーザー操作に対するページの全体的な応答性を測定する指標です。ページの INP が「良好」のしきい値(200 ミリ秒以下)内にある場合、そのページはユーザーの操作に対して確実にレスポンスしていると言えます。

ユーザー操作へのレスポンスとしてイベント コールバックが実行されるのにかかる時間は、インタラクションの合計レイテンシに大きく寄与しますが、考慮すべき INP はそれだけではありません。INP では、インタラクションの次のペイントが発生するまでの時間も考慮されます。これは、操作が完了するまでにユーザー インターフェースを更新するために必要なレンダリング処理にかかる時間です。

ResizeObserver の場合、これは重要です。ResizerObserver インスタンスが実行するコールバックはレンダリング処理の直前に発生するためです。これは設計上のものです。コールバックで発生する処理は考慮する必要があります。その処理の結果、ユーザー インターフェースの変更が必要になる可能性が非常に高いためです。

レンダリング作業を過度に行うと、ブラウザが重要な作業を遅らせる状況が発生する可能性があるため、ResizeObserver コールバックで必要なレンダリング作業を最小限に抑えるようにしてください。たとえば、操作に ResizeObserver コールバックを実行するコールバックが含まれている場合は、可能な限りスムーズに操作できるように、次のことを行います。

  • スタイルの再計算の過剰な作業を回避するために、CSS セレクターをできるだけシンプルにします。スタイルの再計算はレイアウトの直前に行われ、複雑な CSS セレクタはレイアウト オペレーションを遅らせる可能性があります。
  • 強制再フローをトリガーする可能性がある処理を ResizeObserver コールバックで実行しないでください。
  • 通常、ページのレイアウトを更新するのにかかる時間は、ページの DOM 要素の数に比例して増加します。これは、ページが ResizeObserver を使用するかどうかに関係なく当てはまりますが、ページの構造が複雑になるにつれて、ResizeObserver コールバックで実行される処理が著しく大きくなる可能性があります。

まとめ

ResizeObserverすべての主要ブラウザで使用でき、要素レベルで要素のサイズ変更を効率的にモニタリングできます。この強力な API でレンダリングが遅れすぎないように注意してください。