Signatur
Beschreibung
Der Konstruktor new IntersectionObserver() erzeugt einen Beobachter, der die Sichtbarkeit von Elementen relativ zu einem Root überwacht. Das ist entweder der Viewport des Dokuments oder ein scrollbares Vorfahren-Element. Sobald ein beobachtetes Element einen der festgelegten Schwellenwerte (threshold) über- oder unterschreitet, ruft der Browser die übergebene Callback-Funktion mit einem Array von IntersectionObserverEntry-Objekten auf.
Typische Einsatzzwecke sind Lazy Loading von Bildern und Komponenten, Infinite Scrolling, das Messen der Sichtbarkeit von Werbung und das Auslösen von Animationen, sobald ein Element ins Bild scrollt. Im Vergleich zu scroll-Listenern mit getBoundingClientRect() ist die API deutlich performanter, weil die Berechnung außerhalb des Hauptthreads optimiert wird und kein erzwungenes Layout (Reflow) nötig ist.
Nach dem Erzeugen beobachtet der Observer noch nichts: Die zu überwachenden Elemente meldest du mit observe(element) an. Mit unobserve() beendest du die Beobachtung einzelner Elemente, mit disconnect() die aller Elemente. Die Optionen (root, rootMargin, threshold) lassen sich nach dem Erstellen nicht mehr ändern. Brauchst du andere Werte, musst du einen neuen Observer anlegen.
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $callback Pflicht | (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => void | Wird aufgerufen, wenn ein beobachtetes Element einen Schwellenwert überschreitet, und außerdem einmal direkt nach observe() mit dem Anfangszustand. Erhält die Liste der geänderten Einträge und den Observer selbst. |
|
| $options | object | {} | Optionales Konfigurationsobjekt mit den Eigenschaften root, rootMargin und threshold (siehe unten). |
| $options.root | Element | Document | null | null | Bezugsbereich für die Schnittberechnung. Muss ein Vorfahre der beobachteten Elemente sein. null steht für den Viewport des obersten Dokuments. |
| $options.rootMargin | string | "0px 0px 0px 0px" | Rand um den Root im CSS-margin-Format, nur mit px oder %. Positive Werte vergrößern den Erkennungsbereich, z. B. "200px" zum Vorladen, negative verkleinern ihn. |
| $options.threshold | number | number[] | 0 | Ein Sichtbarkeitsanteil oder mehrere Anteile zwischen 0 und 1, bei denen der Callback ausgelöst wird. 0 bedeutet: sobald ein Pixel sichtbar ist. 1 bedeutet: sobald das Element vollständig sichtbar ist. |
| $options.scrollMargin | string | "0px" | Neuere Option: Rand um verschachtelte Scroll-Container. Wird noch nicht in allen Browsern unterstützt. |
Rückgabewert
IntersectionObserver. Er beobachtet erst etwas, wenn du Elemente mit observe() anmeldest.Beispiele
Lazy Loading von Bildern mit Vorlade-Rand
// HTML: <img data-src="foto.jpg" alt="..." class="lazy">
const lazyObserver = new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const img = entry.target;
img.src = img.dataset.src;
img.classList.remove('lazy');
observer.unobserve(img); // Bild nur einmal laden
console.log('Geladen:', img.dataset.src);
}
}, {
rootMargin: '200px 0px' // 200px vor dem Sichtbarwerden laden
});
document.querySelectorAll('img.lazy').forEach((img) => lazyObserver.observe(img));
Einblend-Animation ab 50 % Sichtbarkeit
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
// CSS-Klasse umschalten, sobald mindestens die Hälfte sichtbar ist
entry.target.classList.toggle('sichtbar', entry.intersectionRatio >= 0.5);
console.log(entry.target.id, entry.intersectionRatio.toFixed(2));
});
}, { threshold: [0, 0.5, 1] });
document.querySelectorAll('section').forEach((s) => revealObserver.observe(s));
Infinite Scrolling in einem scrollbaren Container
const liste = document.querySelector('#liste');
const sentinel = document.querySelector('#ende'); // leeres Element am Listenende
let seite = 1;
const ladeMehr = async () => {
const res = await fetch(`/api/artikel?seite=${++seite}`);
const artikel = await res.json();
artikel.forEach(({ titel }) => {
const li = document.createElement('li');
li.textContent = titel;
liste.insertBefore(li, sentinel);
});
if (artikel.length === 0) scrollObserver.disconnect(); // keine weiteren Daten
};
const scrollObserver = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) ladeMehr();
}, { root: liste, rootMargin: '0px 0px 100px 0px' });
scrollObserver.observe(sentinel);
// Wichtig · Fallstricke
- Erster Aufruf: Direkt nach
observe()wird der Callback einmal mit dem Anfangszustand aufgerufen, auch wenn das Element nicht sichtbar ist. Prüfe deshalb immerentry.isIntersecting. - Ungültige Optionen werfen Fehler: Ein
thresholdaußerhalb von 0–1 löst einenRangeErroraus. EinrootMarginmit anderen Einheiten alspxoder%(z. B.em) löst einenSyntaxErroraus. - rootMargin und iframes: Ist
rootgleichnullund das Element liegt in einem Cross-Origin-iframe, wirdrootMarginignoriert. - Keine Pixelgenauigkeit: Die Callbacks laufen asynchron und niedrig priorisiert. Für exakte, frame-genaue Scroll-Effekte eignet sich die API nicht. Dafür sind CSS Scroll-driven Animations besser.
- Speicher: Rufe
unobserve()oderdisconnect()auf, wenn du Elemente nicht mehr brauchst, etwa beim Unmount einer Komponente. - Kompatibilität: Wird von allen modernen Browsern unterstützt (Chrome 51+, Firefox 55+, Safari 12.1+). Der Internet Explorer braucht ein Polyfill.
scrollMarginund die v2-Optionen (trackVisibility,delay) sind nicht überall verfügbar. - Für einfaches Bild-Lazy-Loading reicht oft das native Attribut
loading="lazy".