ResizeObserver
gibt Aufschluss darüber, ob sich die Größe eines Elements ändert.
Vor ResizeObserver
mussten Sie dem resize
-Ereignis des Dokuments einen Listener hinzufügen, um über Änderungen der Viewport-Abmessungen informiert zu werden. Im Ereignishandler müssten Sie dann herausfinden, welche Elemente von dieser Änderung betroffen sind, und eine bestimmte Routine aufrufen, um entsprechend zu reagieren. Wenn Sie nach einer Größenänderung die neuen Abmessungen eines Elements benötigten, mussten Sie getBoundingClientRect()
oder getComputedStyle()
aufrufen. Dies kann zu Layout-Überlastungen führen, wenn Sie nicht alle Lese- und alle Schreibvorgänge in Batches ausführen.
Das Problem trat sogar in Fällen auf, in denen sich Elemente vergrößerten oder verkleinerten, ohne dass das Hauptfenster neu skaliert wurde. Wenn Sie beispielsweise neue untergeordnete Elemente anhängen oder den display
-Stil eines Elements auf none
festlegen, kann sich die Größe eines Elements, seiner Geschwister oder seiner Vorfahren ändern.
Deshalb ist ResizeObserver
ein nützliches Primitive. Er reagiert auf Änderungen der Größe der beobachteten Elemente, unabhängig davon, was die Änderung verursacht hat.
Außerdem erhalten Sie Zugriff auf die neue Größe der beobachteten Elemente.
API
Alle APIs mit dem oben genannten Suffix Observer
haben ein einfaches API-Design. ResizeObserver
ist da keine Ausnahme. Sie erstellen ein ResizeObserver
-Objekt und übergeben dem Konstruktor einen Callback. Dem Rückruf wird ein Array von ResizeObserverEntry
-Objekten übergeben, ein Eintrag pro beobachtetem Element, das die neuen Dimensionen für das Element enthält.
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);
Einige Details
Was wird gemeldet?
In der Regel wird das Inhaltsfeld eines Elements in einem ResizeObserverEntry
über das Attribut contentRect
erfasst, das ein DOMRectReadOnly
-Objekt zurückgibt. Das Inhaltsfeld ist das Feld, in dem Inhalte platziert werden können. Er entspricht dem Begrenzungsrahmen ohne Ränder.
![Ein Diagramm des CSS-Box-Modells.](https://web.dev/static/articles/resize-observer/image/a-diagram-the-css-box-mo-f526084ad034e.png?hl=de)
Hinweis: ResizeObserver
Berichtet zwar sowohl über die Abmessungen der contentRect
als auch über den Abstand, wird aber nur die contentRect
beobachtet.
Verwechseln Sie contentRect
nicht mit dem Begrenzungsrahmen des Elements. Der von getBoundingClientRect()
angegebene Begrenzungsrahmen ist der Rahmen, der das gesamte Element und seine Nachkommen enthält. SVGs sind eine Ausnahme von dieser Regel. Bei ihnen werden mit ResizeObserver
die Abmessungen des Begrenzungsrahmens erfasst.
Ab Chrome 84 hat ResizeObserverEntry
drei neue Properties, die detailliertere Informationen liefern. Jede dieser Properties gibt ein ResizeObserverSize
-Objekt mit einer blockSize
- und einer inlineSize
-Property zurück. Diese Informationen beziehen sich auf das beobachtete Element zum Zeitpunkt des Rückrufs.
borderBoxSize
contentBoxSize
devicePixelContentBoxSize
Für alle diese Elemente werden schreibgeschützte Arrays zurückgegeben, da in Zukunft Elemente mit mehreren Fragmenten unterstützt werden sollen, die in Szenarien mit mehreren Spalten vorkommen. Vorerst enthalten diese Arrays nur ein Element.
Die Plattformunterstützung für diese Properties ist begrenzt, aber Firefox unterstützt bereits die ersten beiden.
Wann wird das Problem gemeldet?
Gemäß der Spezifikation sollten alle Ereignisse zum Ändern der Größe von ResizeObserver
vor dem Malen und nach dem Layout verarbeitet werden. Daher ist der Rückruf einer ResizeObserver
der ideale Ort, um Änderungen am Layout Ihrer Seite vorzunehmen. Da die ResizeObserver
-Verarbeitung zwischen Layout und Paint erfolgt, wird dadurch nur das Layout ungültig, nicht die Paint-Operation.
Gotcha
Sie fragen sich vielleicht: Was passiert, wenn ich die Größe eines beobachteten Elements im Rückruf an ResizeObserver
ändere? Die Antwort lautet: Sie lösen sofort einen weiteren Aufruf des Rückrufs aus. Glücklicherweise gibt es in ResizeObserver
einen Mechanismus, mit dem Endlosschleifen und zyklische Abhängigkeiten vermieden werden. Änderungen werden nur im selben Frame verarbeitet, wenn sich das Element, dessen Größe geändert wurde, tiefer im DOM-Baum befindet als das oberste Element, das im vorherigen Rückruf verarbeitet wurde.
Andernfalls werden sie an den nächsten Frame weitergeleitet.
Anwendung
Mit ResizeObserver
können Sie unter anderem Medienabfragen pro Element implementieren. Wenn Sie Elemente beobachten, können Sie Ihre Design-Breakpoints zwingend definieren und die Stile eines Elements ändern. Im folgenden Beispiel ändert sich der Radius des Rahmens des zweiten Felds entsprechend seiner Breite.
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)'));
Ein weiteres interessantes Beispiel ist ein Chatfenster. Das Problem bei einem typischen Layout für Unterhaltungen von oben nach unten ist die Scrollposition. Damit Nutzer nicht verwirrt werden, sollte das Fenster am unteren Rand der Unterhaltung bleiben, wo die neuesten Nachrichten angezeigt werden. Außerdem sollte dies bei jeder Art von Layoutänderung der Fall sein (z. B. wenn ein Smartphone vom Quer- ins Hochformat wechselt oder umgekehrt).
Mit ResizeObserver
können Sie einen einzigen Codeblock schreiben, der beide Szenarien abdeckt. Das Ändern der Größe des Fensters ist ein Ereignis, das per Definition von ResizeObserver
erfasst werden kann. Wenn Sie jedoch appendChild()
aufrufen, wird auch die Größe dieses Elements geändert (es sei denn, overflow: hidden
ist festgelegt), da Platz für die neuen Elemente geschaffen werden muss. Mit diesem Ansatz benötigen Sie nur sehr wenige Zeilen, um den gewünschten Effekt zu erzielen:
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);
Ziemlich praktisch, oder?
Jetzt könnte ich noch Code hinzufügen, um den Fall zu behandeln, in dem der Nutzer manuell nach oben gescrollt hat und beim Scrollen bei dieser Nachricht bleiben möchte, wenn eine neue Nachricht eingeht.
Ein weiterer Anwendungsfall ist jede Art von benutzerdefiniertem Element, das ein eigenes Layout hat.
Bis ResizeObserver
gab es keine zuverlässige Möglichkeit, benachrichtigt zu werden, wenn sich die Abmessungen ändern, damit die untergeordneten Elemente neu angeordnet werden können.
Auswirkungen auf „Interaction to Next Paint“ (INP)
Interaction to Next Paint (INP) ist ein Messwert, der die Reaktionszeit einer Seite auf Nutzerinteraktionen insgesamt bewertet. Wenn der INP einer Seite den Grenzwert für „gut“ (200 Millisekunden oder weniger) unterschreitet, reagiert die Seite zuverlässig auf die Interaktionen der Nutzer.
Die Zeit, die vergeht, bis Ereignis-Callbacks als Reaktion auf eine Nutzerinteraktion ausgeführt werden, kann zwar erheblich zur Gesamtlatenz einer Interaktion beitragen, ist aber nicht der einzige Aspekt der INP, der berücksichtigt werden muss. Bei INP wird auch die Zeit berücksichtigt, die vergeht, bis die nächste Paint-Aktion der Interaktion erfolgt. Das ist die Zeit, die benötigt wird, um die erforderlichen Rendering-Vorgänge auszuführen, um die Benutzeroberfläche als Reaktion auf eine Interaktion zu aktualisieren.
Bei ResizeObserver
ist das wichtig, da der Rückruf, der von einer ResizerObserver
-Instanz ausgeführt wird, kurz vor dem Rendering ausgeführt wird. Das ist beabsichtigt, da die im Callback ausgeführte Arbeit berücksichtigt werden muss, da das Ergebnis dieser Arbeit höchstwahrscheinlich eine Änderung an der Benutzeroberfläche erfordert.
Achten Sie darauf, in einem ResizeObserver
-Callback so wenig Rendering wie möglich auszuführen, da übermäßige Rendering-Arbeiten zu Verzögerungen bei wichtigen Aufgaben des Browsers führen können. Wenn eine Interaktion beispielsweise einen Callback hat, der einen ResizeObserver
-Callback auslöst, solltest du Folgendes tun, um die Funktionsweise zu optimieren:
- Achten Sie darauf, dass Ihre CSS-Selektoren so einfach wie möglich sind, um unnötige Stilneuberechnungen zu vermeiden. Stilberechnungen erfolgen kurz vor dem Layout und komplexe CSS-Selektoren können Layoutvorgänge verzögern.
- Führen Sie in Ihrem
ResizeObserver
-Callback keine Aktionen aus, die erzwungene Neuformatierungen auslösen können. - Die Zeit, die zum Aktualisieren des Layouts einer Seite erforderlich ist, nimmt im Allgemeinen mit der Anzahl der DOM-Elemente auf einer Seite zu. Das gilt unabhängig davon, ob Seiten
ResizeObserver
verwenden oder nicht. Die Arbeit in einemResizeObserver
-Callback kann jedoch erheblich werden, wenn die strukturelle Komplexität einer Seite zunimmt.
Fazit
ResizeObserver
ist in allen gängigen Browsern verfügbar und bietet eine effiziente Möglichkeit, die Größe von Elementen auf Elementebene zu überwachen. Achten Sie aber darauf, das Rendering mit dieser leistungsstarken API nicht zu sehr zu verzögern.