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

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

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

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

これが、ResizeObserver が有用なプリミティブである理由です。変化の原因に関係なく、観測された要素のサイズ変更に対応します。観測された要素の新しいサイズにもアクセスできます。

対応ブラウザ

  • 64
  • 79
  • 69
  • 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);

詳細

報告される内容

通常、ResizeObserverEntry は、DOMRectReadOnly オブジェクトを返す contentRect というプロパティを介して要素のコンテンツ ボックスを報告します。コンテンツ ボックスは、コンテンツを配置できるボックスです。枠線からパディングを引いた値になります。

CSS ボックスモデルの図。

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

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

  • borderBoxSize
  • contentBoxSize
  • devicePixelContentBoxSize

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

これらのプロパティのプラットフォームはサポートが制限されていますが、Firefox はすでに最初の 2 つをサポートしています。

いつ報告されますか?

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

承知しました

「コールバック内で観測される要素のサイズを ResizeObserver に変更するとどうなるのか」と疑問に思われるかもしれません。その場合は すぐにコールバックへの 別の呼び出しをトリガーします幸いなことに、ResizeObserver には無限のコールバック ループと循環依存関係を回避するメカニズムがあります。サイズ変更された要素が、前回のコールバックで処理された最も浅い要素よりも DOM ツリーの奥深くにある場合にのみ、同じフレーム内で変更が処理されます。そうしないと、次のフレームまで遅延します。

アプリケーション

ResizeObserver を使用してできることの 1 つは、要素ごとのメディアクエリの実装です。要素を監視することで、デザインのブレークポイントを命令的に定義し、要素のスタイルを変更できます。次のでは、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)'));

注目すべきもう 1 つの興味深い例は、チャット ウィンドウです。典型的な上から下への会話レイアウトで生じる問題は、スクロールの位置です。ユーザーの混乱を避けるために、ウィンドウを会話の下部に固定すると、最新のメッセージが表示される場合に便利です。また、どのような種類のレイアウト変更(スマートフォンを横向きから縦向きに、またはその逆に変更)しても、同じ結果を得る必要があります。

ResizeObserver を使用すると、両方のシナリオに対応する 1 つのコードを作成できます。ウィンドウのサイズ変更は、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 を使用してレンダリングを過度に遅らせないように注意してください。