ResizeObserver: Das ist wie „document.onresize“ für Elemente.

ResizeObserver gibt Aufschluss darüber, ob sich die Größe eines Elements ändert.

Vor dem ResizeObserver mussten Sie einen Listener an das resize-Ereignis des Dokuments anhängen, um über Änderungen der Abmessungen des Darstellungsbereichs informiert zu werden. Im Event-Handler müssen 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. Durch das Anhängen neuer untergeordneter Elemente, das Festlegen des Stils display eines Elements auf none oder durch ähnliche Aktionen kann sich beispielsweise die Größe eines Elements, seiner gleichgeordneten Elemente oder seiner Ancestors ä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.

Unterstützte Browser

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

Quelle

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 das Inhalte eingefügt werden können. Es ist das Rahmenfeld abzüglich des Abstands.

Ein Diagramm des CSS-Box-Modells.

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() gemeldete Begrenzungsrahmen ist der Rahmen, der das gesamte Element und seine Nachfolger enthält. SVGs sind eine Ausnahme von der Regel, bei der ResizeObserver die Abmessungen des Begrenzungsrahmens meldet.

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 voraussichtlich Elemente mit mehreren Fragmenten unterstützt werden, die in Szenarien mit mehreren Spalten vorkommen. Vorerst enthalten diese Arrays nur ein Element.

Die Plattformunterstützung für diese Properties ist eingeschränkt, aber Firefox unterstützt bereits die ersten beiden Properties.

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.

Verstanden.

Sie fragen sich vielleicht, was passiert, wenn ich die Größe eines beobachteten Elements innerhalb des Callbacks zu ResizeObserver ändere. Die Antwort lautet: Sie lösen sofort einen weiteren Aufruf an den Callback 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. Durch die Beobachtung von Elementen können Sie Haltepunkte für das Design unbedingt festlegen und die Stile eines Elements ändern. Im folgenden Beispiel ändert sich der Rahmenradius 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 sind nur sehr wenige Zeilen erforderlich, 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-Wert einer Seite unter dem „guten“ Grenzwert liegt, also 200 Millisekunden oder weniger, kann dies besagt, dass eine Seite zuverlässig auf die Interaktionen des Nutzers mit ihr reagiert.

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.

Achte darauf, so wenig Rendering-Arbeiten wie in einem ResizeObserver-Callback auszuführen, da übermäßige Rendering-Arbeit dazu führen kann, dass der Browser wichtige Aufgaben verzögert. Wenn eine Interaktion beispielsweise einen Callback hat, der einen ResizeObserver-Callback auslöst, solltest du Folgendes beachten, um die Funktionsweise zu optimieren:

  • Ihre CSS-Selektoren sollten so einfach wie möglich sein, um eine übermäßige Neuberechnung des Stils 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.
  • Der Zeitaufwand für die Aktualisierung des Layouts einer Seite erhöht sich in der Regel mit der Anzahl der DOM-Elemente auf einer Seite. Dies gilt unabhängig davon, ob Seiten ResizeObserver verwenden oder nicht. Die in einem ResizeObserver-Callback ausgeführte Arbeit kann jedoch bedeutend 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.