ResizeObserver
は、要素のサイズが変更されたときに通知します。
ResizeObserver
以前は、ビューポートのサイズの変更について通知を受け取るには、ドキュメントの resize
イベントにリスナーをアタッチする必要がありました。イベント ハンドラでは、その変更の影響を受けた要素を特定し、適切な対応のために特定のルーティンを呼び出す必要があります。サイズ変更後に要素の新しいディメンションが必要になった場合は、getBoundingClientRect()
または getComputedStyle()
を呼び出す必要があります。すべての読み取りとすべての書き込みをバッチ処理しないと、レイアウト スラッシングが発生する可能性があります。
メイン ウィンドウのサイズ変更なしで要素のサイズを変更するケースもカバーされていませんでした。たとえば、新しい子の追加や、要素の display
スタイルを none
に設定するなどのアクションによって、要素、兄弟要素、祖先のサイズを変更できます。
これが、ResizeObserver
が有用なプリミティブである理由です。変化の原因に関係なく、観測された要素のサイズ変更に対応します。観測された要素の新しいサイズにもアクセスできます。
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
というプロパティを介して要素のコンテンツ ボックスを報告します。コンテンツ ボックスは、コンテンツを配置できるボックスです。枠線からパディングを引いた値になります。
ResizeObserver
は contentRect
とパディングの両方のディメンションをレポートしますが、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 を使用してレンダリングを過度に遅らせないように注意してください。