ResizeObserverはウィンドウではなく要素のサイズ変化を監視します。コールバックでレイアウトを変えると、次の計算で観察サイズも変わり、再通知されることがあります。CSS Working GroupのResize Observer草案は観察boxと通知配送の規則を定義し、以下ではMDNとChrome Developersの実装解説と結び付けます。

観察対象の変更が次のサイズ通知につながる概念図

概念図であり、ブラウザー測定の証拠ではありません。

観察するboxと報告サイズ

observe(target, {box})のboxは、どのCSS boxの変更で通知するかを選びます。ResizeObserverEntryにはcontentBoxSize、borderBoxSize、devicePixelContentBoxSizeが別々にあります。inlineSizeとblockSizeは書字方向に沿う論理軸で、常に物理的な幅・高さとは限りません。複数fragmentを表せる配列形状ですが、常に複数要素が入るとは考えないでください。contentRectは従来の物理軸表現として区別します。

コールバックの書き込みで再通知

コールバックが幅を変え、その変更が観察boxに影響すると、再レイアウトで次の通知が発生します。一度の更新中に通知が無限に続かないよう配送処理には制限があり、未配送の観察が次回へ送られるとResizeObserver loopエラーが報告されることがあります。エラーを隠しても依存関係は解消しません。

収束する更新にする

無条件に測定と書き込みを往復せず、目標値が前回適用値と異なるときだけ更新します。このguardは実務的な例で、親子の相互依存が必ず安定する証明ではありません。互いにサイズを決め続けると、フレームをまたいで振動することがあります。requestAnimationFrameで遅らせても依存関係自体はなくなりません。

必要なboxだけ読み後始末する

実際に使うcontent boxかborder boxを監視しているか、書字方向とbox-sizingを確認します。device-pixelサイズはピクセル単位の用途に合いますがCSSピクセルと同じとは限りません。要素やコンポーネントが破棄されるときunobserveまたはdisconnectを呼びます。ResizeObserverは要素サイズの変化を伝えるAPIであり、CSS transformやあらゆる見た目の差を検出するものではありません。

ループの原因を切り分ける

コールバックが読む値と書くCSSプロパティを記録し、どの書き込みが次の観察サイズを変えるか調べます。たとえばcontent幅を読んで親幅を広げると、子の折り返しと高さが変わり、双方が影響し合うことがあります。スクリプト計算が不要ならCSS grid、flexbox、container queryで関係を表す方が簡潔です。ResizeObserverを毎フレームのレイアウトpollingに使うものではありません。

一度のloop警告後に次の描画機会へ通知が送られる現象と、アプリが毎frameサイズを反転させる現象は異なります。ログを消すために毎回disconnectすると原因を隠すだけです。目標値を制限し、同値の書き込みを避け、観察boxがコンポーネントのcontent/border boxと合うか確認します。

仮想のサイズ追跡で自己参照の書き込みを見つける

実行結果ではなく、説明用の追跡を考えます。横書き、content-box、paddingとborderなし、観察対象のcontent幅が最初に320pxと仮定します。コールバックがinlineSizeを読み、1px足した値を同じ要素のCSS widthへ書くと、次の観察値が321px、その次が322pxになることがあります。この数値は規則から導いた仮想の入力と計算例であり、ブラウザーの測定値ではありません。読む対象と書き換える対象が同じで、安定した目標がない点が問題です。

実際の診断では、コールバックが読む値、観察box、書き換えるCSSプロパティ、書き込み前後の目標値を記録し、どこで変化が戻ってくるか切り分けます。同じ値の書き込みを省くguardは重複更新を減らせますが、毎回1pxずつ目標が変わる規則を収束させるものではありません。可能なら観察対象自身ではなく、変化しない親の条件から目標を計算してください。レイアウトだけで表せる反応にはCSS grid、flexbox、container queryを使います。Resize Observer草案のdepth制限と未配送通知のエラーは通知手順を制限するもので、アプリのフィードバック関係を自動修正する機能ではありません。

参考資料