JavaScript Resize Observer API
Lerne die JavaScript Resize Observer API kennen, um auf Größenänderungen einzelner Elemente zu reagieren — für responsive Komponenten, Canvas- und Diagramm-Anpassungen sowie automatisch wachsende Eingabefelder.
Die ResizeObserver API ermöglicht es dir zu reagieren, wenn ein bestimmtes Element seine Größe ändert – unabhängig vom Grund. Sie gehört zur gleichen Familie wie der MutationObserver (der den DOM-Baum beobachtet) und der IntersectionObserver (der die Sichtbarkeit überwacht): effiziente, callback-basierte Observer, die umständliche Polling-Schleifen ersetzen.
Warum nicht einfach das resize-Event nutzen?
Das resize-Event des window-Objekts wird nur ausgelöst, wenn sich die Größe des Browserfensters ändert. Ein Element auf der Seite kann jedoch aus vielen anderen Gründen seine Größe ändern:
- Eine CSS-Regel ändert sich (eine Media Query greift, eine Klasse wird umgeschaltet).
- Der Inhalt ändert sich (Text wird geladen, ein Bild kommt an, Zeilen werden einer Liste hinzugefügt).
- Ein Flexbox- oder CSS Grid-Layout fließt um, weil ein Geschwisterelement gewachsen oder geschrumpft ist.
- Ein übergeordneter Container wird durch einen ziehbaren Trenner, eine Seitenleiste oder ein Panel in der Größe verändert.
Keines davon ändert zwingend die Fenstergröße, weshalb window.addEventListener('resize', ...) nie ausgelöst wird. Man könnte mit setInterval pollen und alle paar Millisekunden getBoundingClientRect() vergleichen, aber das verschwendet CPU-Ressourcen und hinkt der tatsächlichen Änderung hinterher.
ResizeObserver löst dieses Problem: Es beobachtet ein oder mehrere Elemente und ruft dich nur dann zurück, wenn sich deren Größe tatsächlich ändert.
ResizeObserver meldet standardmäßig die Inhaltsgröße eines Elements, nicht dessen Position. Wenn du wissen möchtest, wann ein Element verschoben wird oder in den sichtbaren Bereich scrollt, nutze stattdessen den IntersectionObserver. Für Messungen auf viewport-Ebene, siehe Fenstergrößen und Scrollen.
Grundlegende Verwendung
Das Muster entspricht dem der anderen Observer: Erstelle einen Observer mit einem Callback und teile ihm mit, welche(s) Element(e) er observen soll.
const box = document.querySelector('#box');
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
console.log(`${entry.target.id} is now ${width} x ${height}`);
}
});
ro.observe(box);Der Callback erhält ein array von Einträgen – einen pro beobachtetem Element, das sich in diesem Batch geändert hat – sodass ein einzelner Observer viele Elemente gleichzeitig überwachen kann.
Was ein Eintrag enthält
Jeder Eintrag beschreibt ein Element und seine neue Größe. Es gibt zwei Möglichkeiten, diese Größe auszulesen.
Die einfache Methode ist entry.contentRect, ein DOMRectReadOnly mit width, height, top und left (die Abstände sind relativ zur Padding-Box des Elements):
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
console.log(entry.target); // the element
console.log(entry.contentRect.width); // content-box width in px
console.log(entry.contentRect.height); // content-box height in px
}
});Die präzisere Methode nutzt die Box-Size-Eigenschaften, die arrays von { inlineSize, blockSize }-Objekten sind (ein array, weil ein Element über mehrere Spalten verteilt sein kann):
entry.contentBoxSize— die Content-Box, ohne Padding und Border.entry.borderBoxSize— die Border-Box, einschließlich Padding und Border.entry.devicePixelContentBoxSize— die Content-Box gemessen in Geräte-Pixeln, ideal für scharfes Canvas-Rendering auf Bildschirmen mit hoher Pixeldichte.
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
// inlineSize ~ width, blockSize ~ height (for a horizontal writing mode)
const { inlineSize, blockSize } = entry.borderBoxSize[0];
console.log(`border box: ${inlineSize} x ${blockSize}`);
}
});Standardmäßig reagiert der Observer auf Änderungen der Content-Box. Um stattdessen die Border-Box zu beobachten, übergib ein Options-Objekt:
ro.observe(box, { box: 'border-box' });inlineSize und blockSize berücksichtigen den Schreibmodus. Im standardmäßigen Links-nach-Rechts-, Oben-nach-Unten-Modus entspricht inlineSize der Breite und blockSize der Höhe – in einem vertikalen Schreibmodus tauschen sie jedoch. Bevorzuge sie gegenüber hartkodierten width/height-Werten, wenn deine Benutzeroberfläche mehrere Schreibrichtungen unterstützen muss.
Methoden
ResizeObserver bietet drei Methoden:
observe(element, options)— startet die Beobachtung eines Elements. Einmal pro Element aufrufen. Das optionaleoptions.boxkann'content-box'(Standard),'border-box'oder'device-pixel-content-box'sein.unobserve(element)— beendet die Beobachtung eines einzelnen Elements.disconnect()— beendet die Beobachtung aller Elemente auf einmal.
ro.observe(el); // start
ro.unobserve(el); // stop watching this one
ro.disconnect(); // stop watching everythingAnwendungsfall 1 — Responsive Komponenten („Container Queries in JS")
Eine Media Query reagiert auf den viewport. Eine wiederverwendbare Karte könnte jedoch in der Hauptspalte breit und in einer Seitenleiste schmal sein – bei exakt derselben viewport-Breite. Mit ResizeObserver kannst du eine Komponente an ihre eigene Breite anpassen:
const card = document.querySelector('.card');
const ro = new ResizeObserver(([entry]) => {
const width = entry.contentRect.width;
// Toggle a layout class based on the element's own width.
card.classList.toggle('card--compact', width < 400);
});
ro.observe(card);.card { display: flex; gap: 1rem; }
.card--compact { flex-direction: column; }Nun stapelt die Karte ihre Inhalte vertikal, wann immer sie schmaler als 400px ist – unabhängig von der Fenstergröße. Praktisch in Dashboards, geteilten Bereichen und einbettbaren Widgets.
Modernes CSS kann dies nativ mit Container Queries (@container) umsetzen. Wenn du nur das Styling anhand der Größe eines Containers ändern möchtest, bevorzuge CSS Container Queries – sie sind deklarativ und laufen außerhalb des Hauptthreads. Greife auf ResizeObserver zurück, wenn du als Reaktion auf die Größenänderung JavaScript ausführen musst (Werte neu berechnen, Canvas neu zeichnen, Diagramm neu anordnen).
Anwendungsfall 2 — Größenanpassung eines Canvas oder Diagramms
Ein <canvas> hat zwei Größen: seine CSS-Darstellungsgröße und seine Zeichenpuffergröße (canvas.width/canvas.height). Stimmen sie nicht überein, wird die Bitmap gestreckt und wirkt verschwommen. ResizeObserver hält den Puffer mit der angezeigten Größe synchron, damit Zeichnungen scharf bleiben:
const canvas = document.querySelector('#chart');
const ctx = canvas.getContext('2d');
const ro = new ResizeObserver(([entry]) => {
// Use device-pixel size for sharp rendering on high-DPI screens.
const size = entry.devicePixelContentBoxSize?.[0];
const width = size ? size.inlineSize : entry.contentRect.width;
const height = size ? size.blockSize : entry.contentRect.height;
canvas.width = width;
canvas.height = height;
redraw(ctx, width, height); // your drawing / charting code
});
ro.observe(canvas, { box: 'device-pixel-content-box' });Das gleiche Prinzip gilt für Diagramm-Bibliotheken: Beobachte den Container des Diagramms und rufe die resize()-Methode der Bibliothek auf, wenn sich der Container ändert, anstatt nur auf window.resize zu hören.
Anwendungsfall 3 — Automatisch wachsendes Textarea
Da der Callback immer dann ausgelöst wird, wenn sich die gemessene Größe des Elements ändert, kannst du abhängige Elemente synchron halten. Ein klassisches Beispiel ist das Spiegeln der Höhe einer Textarea auf ein Geschwisterelement oder das Reagieren auf inhaltsbedingtes Wachstum:
const textarea = document.querySelector('#message');
const counter = document.querySelector('#height-readout');
const ro = new ResizeObserver(([entry]) => {
const h = Math.round(entry.contentRect.height);
counter.textContent = `${h}px tall`;
});
ro.observe(textarea);Immer wenn der Benutzer am Größenänderungs-Handle der Textarea zieht – oder dein Code deren Höhe beim Tippen ändert – wird die Anzeige automatisch aktualisiert, ohne resize-Event und ohne Polling.
Die „ResizeObserver loop"-Warnung
Möglicherweise begegnest du dieser Konsolenmeldung:
ResizeObserver loop completed with undelivered notifications.
(Einige Browser formulieren es als „ResizeObserver loop limit exceeded.") Sie tritt auf, wenn dein Callback die Größe des beobachteten Elements verändert, was den Observer erneut auslöst, was die Größe wieder verändert – eine Rückkopplungsschleife.
// Anti-pattern: this can cause the loop warning.
const ro = new ResizeObserver(([entry]) => {
// Resizing the observed element from inside its own callback. Bad.
entry.target.style.height = entry.contentRect.width + 'px';
});Verändere die Größe des beobachteten Elements nicht synchron innerhalb seines eigenen Callbacks. Wenn eine größenbedingte Größenänderung unvermeidlich ist, unterbreche den Kreislauf: Schreibe nur, wenn sich der Wert tatsächlich geändert hat (eine Schutzabfrage), oder verzögere das Schreiben mit requestAnimationFrame. Die Warnung ist meist harmlos und korrigiert sich selbst, aber eine echte Endlosschleife beeinträchtigt die Performance.
Aufräumen
Ein Observer hält eine Referenz auf jedes beobachtete Element, was verhindern kann, dass das Element vom Garbage Collector erfasst wird. Beende die Beobachtung immer, wenn du fertig bist – zum Beispiel wenn eine Komponente ausgehängt oder eine Ansicht zerstört wird:
function mountWidget(el) {
const ro = new ResizeObserver(handleResize);
ro.observe(el);
// Return a cleanup function.
return () => ro.disconnect();
}Das Vergessen dieser Bereinigung ist eine häufige Ursache für Speicherlecks in Single-Page-Apps. Weitere Informationen zum effizienten Umgang mit Größen- und Layout-Berechnungen findest du unter DOM-Performance-Optimierung.
ResizeObserver wird von allen modernen Browsern unterstützt, sodass du dich ohne Polyfill auf aktuelle Evergreen-Browser verlassen kannst.