Signatur
Beschreibung
Das Interface IntersectionObserver der Intersection Observer API meldet asynchron, wenn sich die Überschneidung (Intersection) eines Ziel-Elements mit einem Wurzel-Element (root) ändert. Die Wurzel ist entweder der Viewport des Dokuments auf oberster Ebene oder ein übergeordnetes, scrollbares Element. Der Browser ruft Ihre Callback-Funktion nur auf, wenn eine der konfigurierten Schwellen (threshold) überschritten wird. So müssen Sie scroll-Events nicht selbst auswerten und getBoundingClientRect() nicht ständig aufrufen.
Typische Einsatzfälle:
- Lazy Loading von Bildern, iframes oder Komponenten, sobald sie in die Nähe des Viewports kommen
- Endloses Scrollen (Infinite Scroll) mit einem Sentinel-Element am Listenende
- Sichtbarkeits-Tracking (z. B. Werbeeinblendungen, Lesefortschritt, Analytics)
- Scroll-Animationen, die starten, sobald ein Abschnitt sichtbar wird
- Pausieren von Videos oder Animationen außerhalb des sichtbaren Bereichs
Sie erzeugen einen Observer mit new IntersectionObserver(callback, options) und registrieren Ziele mit observe(element). Ein einziger Observer kann beliebig viele Elemente beobachten. Die Callback-Funktion bekommt ein Array von IntersectionObserverEntry-Objekten mit Eigenschaften wie isIntersecting, intersectionRatio, boundingClientRect und target. Mit unobserve() beenden Sie die Beobachtung eines einzelnen Elements, mit disconnect() die aller Elemente. takeRecords() liefert noch nicht zugestellte Einträge sofort.
Schreibgeschützte Eigenschaften der Instanz: root, rootMargin, thresholds (immer ein sortiertes Array) sowie in neueren Browsern delay und trackVisibility (Intersection Observer v2).
Parameter
| Name | Typ | Default | Beschreibung |
|---|---|---|---|
| $callback Pflicht | (entries: IntersectionObserverEntry[], observer: IntersectionObserver) => void | Wird aufgerufen, wenn sich der Überschneidungsstatus eines beobachteten Elements über eine Schwelle hinweg ändert, und einmal direkt nach observe() mit dem Anfangszustand. |
|
| $options | object | {} | Konfigurationsobjekt mit den folgenden Feldern. |
| $options.root | Element | Document | null | null | Wurzel-Element, gegen das die Überschneidung berechnet wird. Muss ein Vorfahre der Ziele sein. null steht für den Viewport des Dokuments auf oberster Ebene. |
| $options.rootMargin | string | "0px 0px 0px 0px" | Vergrößert oder verkleinert den Wurzelbereich, Syntax wie bei CSS margin (nur px und %). Beispiel: "200px 0px" löst 200 px vor dem Sichtbarwerden aus. |
| $options.threshold | number | number[] | 0 | Einzelwert oder Array von Werten zwischen 0 und 1. Gibt an, bei welchen sichtbaren Anteilen die Callback-Funktion ausgelöst wird. 0 heißt: sobald ein einziges Pixel sichtbar ist, 1 heißt: vollständig sichtbar. |
| $options.scrollMargin | string | "0px" | Neuer Standard: Rand um verschachtelte Scroll-Container, damit Elemente in inneren Scroll-Bereichen früher erkannt werden. Wird noch nicht von allen Browsern unterstützt. |
| $options.trackVisibility | boolean | false | Intersection Observer v2 (nur Chromium): prüft zusätzlich, ob das Element tatsächlich sichtbar ist (nicht verdeckt, nicht transparent). Setzt delay ≥ 100 voraus. |
| $options.delay | number | 0 | Minimaler Abstand zwischen Benachrichtigungen in Millisekunden (v2, nur Chromium). Pflicht, wenn trackVisibility aktiv ist. |
Rückgabewert
observe() aufgerufen haben. Methoden: observe(target), unobserve(target), disconnect(), takeRecords().Beispiele
Lazy Loading von Bildern mit Vorlauf
// 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); // nur einmal laden
}
}, {
rootMargin: '200px 0px', // 200 px vor dem Sichtbarwerden laden
threshold: 0
});
document.querySelectorAll('img.lazy').forEach((img) => lazyObserver.observe(img));
Infinite Scroll mit Sentinel-Element
const list = document.querySelector('#liste');
const sentinel = document.querySelector('#sentinel'); // leeres <div> am Ende
let page = 1;
let loading = false;
const loadMore = async () => {
loading = true;
const res = await fetch(`/api/artikel?page=${page++}`);
const items = await res.json();
if (items.length === 0) {
infiniteObserver.disconnect(); // keine weiteren Daten
return;
}
for (const item of items) {
const li = document.createElement('li');
li.textContent = item.title;
list.append(li);
}
loading = false;
};
const infiniteObserver = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && !loading) loadMore();
});
infiniteObserver.observe(sentinel);
Sichtbarkeitsanteil in einem Scroll-Container messen
const container = document.querySelector('.scroll-box');
const box = document.querySelector('.scroll-box .ziel');
const ratioObserver = new IntersectionObserver((entries) => {
for (const entry of entries) {
const percent = Math.round(entry.intersectionRatio * 100);
console.log(`${entry.target.className}: ${percent} % sichtbar`);
entry.target.style.opacity = entry.intersectionRatio;
}
}, {
root: container,
threshold: Array.from({ length: 11 }, (_, i) => i / 10) // 0, 0.1, … 1
});
ratioObserver.observe(box);
console.log(ratioObserver.thresholds.length);
Scroll-Animation einmalig auslösen
const revealObserver = new IntersectionObserver((entries, obs) => {
entries
.filter((e) => e.isIntersecting)
.forEach((e) => {
e.target.classList.add('sichtbar'); // CSS-Transition startet
obs.unobserve(e.target);
});
}, { threshold: 0.25 });
document.querySelectorAll('.reveal').forEach((el) => revealObserver.observe(el));
// Aufräumen, z. B. beim Verlassen einer SPA-Route
window.addEventListener('pagehide', () => revealObserver.disconnect());