// apis · Web Platform Advent #10
Die IntersectionObserver-API: Lazy-Loading und Scroll-Effekte
Nutze IntersectionObserver, um Bilder per Lazy-Loading zu laden, Elemente beim Scrollen einzublenden und Infinite Scroll zu bauen — mit Erklärung von threshold und rootMargin sowie lauffähigen Beispielen.
Die IntersectionObserver-API sagt dir, wann ein Element in den Viewport eintritt oder ihn verlässt — ohne dass du einen scroll-Listener verdrahten und in jedem Frame Positionen berechnen musst. Der Browser übernimmt die Überwachung asynchron und außerhalb des Hauptthreads, sodass alles flüssig bleibt, selbst bei Hunderten von Zielen. Jeder aktuelle Browser unterstützt sie.
Warum nicht einfach auf scroll lauschen?
Ein scroll-Handler feuert ständig und zwingt dich, getBoundingClientRect() aufzurufen, um zu wissen, wo sich die Dinge befinden — das liest das Layout aus und kann zu Rucklern führen. IntersectionObserver dreht das Modell um: Du registrierst die Elemente, die dich interessieren, und der Browser benachrichtigt dich nur, wenn sich ihre Sichtbarkeit tatsächlich ändert.
Das grundlegende Muster
Erstelle einen Observer mit einem Callback und rufe dann observe() für ein oder mehrere Elemente auf. Der Callback erhält ein Array von entry-Objekten; die wichtigste Eigenschaft ist entry.isIntersecting:
const observer = new IntersectionObserver((entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
console.log('Now visible:', entry.target);
}
}
});
document.querySelectorAll('.watch').forEach((el) => observer.observe(el)); Bilder per Lazy-Loading laden
Ein klassischer Einsatz: Ein Bild erst laden, wenn es kurz davor ist, in den sichtbaren Bereich zu scrollen. Speichere die echte URL in data-src, tausche sie ein, sobald das Element sichtbar wird, und beende dann die Beobachtung:
const lazyImages = document.querySelectorAll('img[data-src]');
const imgObserver = new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
const img = entry.target;
img.src = img.dataset.src;
observer.unobserve(img); // load once, then stop watching
}
}, { rootMargin: '200px' });
lazyImages.forEach((img) => imgObserver.observe(img)); Hinweis: Natives Lazy-Loading über <img loading="lazy"> deckt heute die meisten Fälle ab. Greife zu IntersectionObserver, wenn du eigene Logik brauchst — Platzhalter, Einblendungen oder das Laden von Nicht-Bild-Ressourcen.
Elemente beim Scrollen einblenden
Füge eine CSS-Klasse hinzu, sobald ein Element zum ersten Mal sichtbar wird, damit es eingeblendet werden kann. Verwende die Option threshold, um zu entscheiden, wie viel des Elements sichtbar sein muss, bevor der Callback feuert:
const revealObserver = new IntersectionObserver((entries, observer) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
observer.unobserve(entry.target);
}
}
}, { threshold: 0.2 }); // fire when 20% is on screen
document.querySelectorAll('.reveal').forEach((el) => revealObserver.observe(el)); Infinite Scroll
Platziere ein leeres Sentinel-Element hinter deiner Liste. Wenn es in den Viewport eintritt, lade die nächste Seite und füge sie an, und beobachte dann weiter für die darauffolgende Seite:
const sentinel = document.querySelector('#sentinel');
let page = 1;
const loader = new IntersectionObserver(async (entries) => {
if (!entries[0].isIntersecting) return;
const items = await fetchPage(page++);
appendItems(items);
});
loader.observe(sentinel); Die Optionen: threshold und rootMargin
Zwei Optionen bestimmen, wann der Callback feuert:
threshold— eine Zahl (oder ein Array) von 0 bis 1.0feuert in dem Moment, in dem ein Pixel sichtbar ist;1feuert erst, wenn das gesamte Element sichtbar ist. Ein Array wie[0, 0.5, 1]feuert bei jedem Schritt, praktisch für Scroll-Fortschrittseffekte.rootMargin— ein Rand im CSS-Stil, der den für den Test verwendeten Bereich vergrößert oder verkleinert.'200px'löst 200px bevor das Element den Viewport erreicht aus — ideal zum Vorladen.root— das Element, das als Viewport dienen soll. Standardmäßig der Browser-Viewport; setze es auf einen scrollbaren Container, um stattdessen darin zu beobachten.
Aufräumen
Beende die Beobachtung eines einzelnen Elements mit observer.unobserve(el) oder baue alles ab mit observer.disconnect() — wichtig in Single-Page-Apps, wenn eine Ansicht ausgehängt wird, um Lecks zu vermeiden.
Kurzreferenz
| Ziel | Wichtige Option / Aufruf |
|---|---|
| Sichtbarkeit erkennen | entry.isIntersecting |
| Früh vorladen | rootMargin: '200px' |
| Warten bis X% sichtbar | threshold: 0.5 |
| Innerhalb eines Containers beobachten | root: containerEl |
| Eines stoppen | observer.unobserve(el) |
| Alles abbauen | observer.disconnect() |
IntersectionObserver ersetzt eine ganze Kategorie fragiler Scroll-Berechnungen durch eine kleine, deklarative API. Lazy-Bilder, Scroll-Einblendungen und unendliche Listen lassen sich alle auf dasselbe Muster reduzieren: beobachten, auf isIntersecting reagieren und die Beobachtung beenden, wenn du fertig bist.