Intersection Observer
Intersection Observer API auf mdn
Die Intersection Observer API nutzen wir, um herauszufinden, ob (bzw. wann) sich zwei Elemente überschneiden. Wir verwenden das z.B.
- für einen Header, der am oberen Bildschirmrand fixiert wird, und der die Hintergrundfarbe ändern soll, sobald ein nachfolgendes Element unter den Header scrollt
- für Elemente, die in den Viewport hineinsliden, sobald das Element den Viewport betritt
- für Bilder, die erst dann geladen werden, wenn sie im Viewport sichtbar werden (bzw. kurz bevor sie sichtbar werden)
Um den Intersection Observer zu verwenden, brauchen wir also Minimum zwei Elemente:
- ein Element, das beobachtet werden soll (das Zielelement bzw. das
target) - ein Element, das überschnitten wird (das kann ein Element sein oder, wenn keines definiert ist, der Viewport, das ist das Wurzelelement bzw. das
root)
Als erstes deklarieren wir für beide Elemente eine Variable:
// unser root-Elementconst root = document.querySelector('.root');// unser target-Elementconst target = document.querySelector('.target');Die Intersection Observer API erlaubt es uns, eine Callback-Funktion zu schreiben. Diese wird aufgerufen, wenn
- eine Überschneidung (die in der Callback-Funktion genauer definiert wird) beobachtet wird (das kann öfter passieren, je nachdem, wieviele Elemente beobachtet werden), und
- die
observe-Methode erstmals aufgerufen wird (das passiert nur ein Mal)
Dazu instanziieren wir als erstes einen neuen Intersection Observer.
Neue Intersection Observer Instanz
Section titled “Neue Intersection Observer Instanz”Wir rufen den Konstruktur mit new auf und übergeben ihm die oben angesprochene Callback-Funktion, die er ausführen soll, sobald er eine Überschneidung feststellt:
const observer = new IntersectionObserver(callback, options);Intersection Observer Optionen
Section titled “Intersection Observer Optionen”Mit dem options-Objekt, das dem IntersectionObserver()-Konstruktor übergeben wird, können wir die genaueren Umstände definieren, wann die Callback-Funktion aufgerufen wird.
const options = { root: null, // default rootMargin: '0px 0px 0px 0px', // default scrollMargin: '0px', // default threshold: 0, // default}Die Optionen im Detail:
Section titled “Die Optionen im Detail:”root: Dieses Element fungiert als Viewport, in dem die Sichtbarkeit des Zielelementes geprüft wird. Es muss ein Vorfahr des Zielelements sein. Wennnulloder nicht angegeben, ist dasroot-Element gleich dem Browser Viewport.rootMargin: wie mit CSS-Margin (Werte-Reihenfolgetop,right,bottom,left) können die Grenzen des Root-Elements nach innen (negative Werte) oder nach außen (positive Werte) verschoben werden. Die Zone, in der die Überschneidung kontrolliert wird, kann damit ausgedehnt oder verringert werden. Erlaubt sind nur Pixel oder Prozentwerte.scrollMargin: ein Margin um verschachtelte Scroll-Container, gleich wierootMargin.threshold: Grenzwert, kann einen Wert zwischen 0 (Elemente überschneiden sich nicht) und 1 (Element ist vollständig im Viewport desroot-Elements). 1 kann kritisch werden, wenn dastarget-Element sehr groß ist und somit nie als ganzes sichtbar sein kann (Mobilversionen beachten!).thresholdkann eine Zahl sein oder ein Array von Werten. Z.B. könnte man benachrichtigt werden wollen, wenn ein Element zu 25%, zu 50%, zu 75% und zu 100% im Überschneidungsbereich liegt.
Callback-Funktion
Section titled “Callback-Funktion”Die Callback-Funktion, die dem IntersectionObserver()-Konstruktur übergeben wird, erhält eine Liste von IntersectionObserverEntry-Objekten und den observer selbst:
const callback = (entries, observer) => { entries.forEach((entry) => { // jede Erfassung beschreibt eine Änderung der Überschneidung für ein beobachtetes target-Element: entry.boundingClientRect entry.intersectionRatio entry.intersectionRect entry.isIntersecting entry.rootBounds entry.target entry.time });}Für jedes Grenzübertritt-Event empfängt die Callback-Funktion ein IntersectionObserverEntry-Objekt. Mehrere gleichzeitige Erfassungen (entry) sind möglich, entweder von verschiedenen Zielelementen oder von einem einzelnen Element, das mehrere Grenzen innerhalb eines kurzen Zeitraums überschreitet. Die Events werden in eine Schlange eingereiht, werden also nach der Zeit, in der sie generiert wurden, geordnet.
Jedes entry beschreibt, wie weit das aktuell erfasste Element mit dem root-Element überlappt, ob das Element überhaupt überlappt etc.
Für uns ist erst einmal das entry.isIntersecting-Objekt interessant. Abhängig davon, ob das target-Element das root-Element überlappt oder nicht (und wie weit), können wir Dinge erledigen lassen:
const callback = (entries, observer) { entries.forEach((entry) => { // immer erst einmal prüfen, ob alles sitzt: console.log(entry); if (entry.isIntersecting) { // eventuell nochmal das Zielelement selbst loggen: console.log(entry.target); // do sth. } });}Zuletzt müssen wir noch den Observer aufrufen und ihm sagen, welches Element er beobachten soll:
observer.observe(target);
// Jetzt wird, wie oben schon beschrieben, die Callback-Funktion erstmals aufgerufen um die Situation abzuchecken, in weiterer Folge immer dann, wenn ein Übertritt beobachtet wird.Methoden der Instanz
Section titled “Methoden der Instanz”IntersectionObserver.unobserve() stoppt die Beobachtung eines bestimmten Zielelements.
Alles zusammen …
Section titled “Alles zusammen …”const root = document.querySelector('.root');const target = document.querySelector('.target');
const options = { rootMarginTop: '90px',}
const callback = (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { root.classList.add('.intersected'); } });}
const observer = new IntersectionObserver(callback, options);
observer.observe(target);A graphical introduction to the Intersection Observer API
Was wir minimal tun müssen:
- einen Observer instantiieren
- ein
targetobservieren
const callback = (entries, observer) => {};
// ohne Optionen werden die default-Werte verwendet:// {threshold: 0, rootMargin: '0px 0px 0px 0px'}
const observer = new IntersectionObserver(callback);const target = document.querySelector('.blubb');observer.observe(target);Die observe Methode
Section titled “Die observe Methode”Diese Methode sagt dem Observer, dass er
- das
targettracken und - sobald eine Intersection passiert, die Callback-Funktion ausführen soll.
Die Observer-Instanz wird basierend auf den Optionen, die beim instanziieren injected wurden, ausgeführt.
Mehrere Ziele tracken
Section titled “Mehrere Ziele tracken”Wenn mehrere Ziele getrackt werden sollen, geht das ganz einfach mit einem Array und einer forEach-Schleife:
const multipleTargets = [...document.querySelectorAll('.blubb')];multipleTargets.forEach((target) => observer.observe(target));Den IntersectionObserver instanziieren
Section titled “Den IntersectionObserver instanziieren”Wenn wir einen IntersectionObserver mit new instantiieren, können wir den zweiten Parameter, die Optionen, weglassen, und nur die Callback-Funktion übergeben. Aber mit Optionen können wir mehr Möglichkeiten ausschöpfen:
const observer = new IntersectionObserver(callback, { threshold: 1, rootMargin: '-100px 0px -250px 0px',});Die Callback-Funktion
Section titled “Die Callback-Funktion”Die Callback-Funktion ist asynchron und kann eine gute, alte JavaScript-Funktion mit zwei Parametern sein:
const callback = (entries, observer) => { entries.forEach((entry) => { // Validate the entry's intersection here and make decision });}Parameter
Section titled “Parameter”entries: Eine Liste vonIntersectionObserverEntry-Objekten, oder, anders gesagt, eine Liste von Elementen, die bezüglich einer Intersection observiert werden, begleitet von relevanten Daten, um Entscheidungen über die Intersection zu machen (unter anderemisIntersecting,intersectionRatio,target, …)observer: die Instanz desIntersectionObserver, der das Element observiert, das die Intersection gerade festgestellt hat. Wird meist mitunobservebenutzt, wenn die Aktion nur einmal ausgeführt werden soll.
In der Callback-Funktion spielt sich der ganze Zauber ab. Dort werden wir über die überlappenden Ziele benachrichtigt, basierend auf unseren Einstellungen. Aber es ist wichtig zu wissen, dass die Callback-Funktion in zwei verschiedenen Szenarien ausgeführt wird:
- Unmittelbar nach dem Aufruf der
observe-Methode versucht der Browser eine untätige Periode zu finden und die Callback-Funktion ausführen, unabhängig davon, ob sich dastargetmit demrootüberschneidet oder nicht. Das bietet die Gelegenheit, frühzeitig festzustellen, ob sich Elemente überlappen (oder nicht) und entsprechend denIntersectionObserverEntry-Parametern zu entscheiden. Dieser Vorgang läuft nur einmal ab. - Jedes Mal, wenn ein
targetsich mit demroot, basierend auf den konfigurierten Optionen, überschneidet. Das kann mehrere Male passieren.
Der IntersectionObserverEntry
Section titled “Der IntersectionObserverEntry”Immer wenn die Callback-Funktion ausgeführt wird, wird ein Array von Entries empfangen. Dafür können verschiedenste Attribute verwendet werden, aber das sind die häufigsten/wichtigsten:
isIntersecting: ein boolscher Wert, der wahr wird, wenntargetsich mit demrootüberschneidet.intersectionRatio: ein Zahlenwertnumberzwischen 0 und 1, die die Position destarget, das sich mit den Grenzen vonrootüberlappt, zur Verfügung stellt. Das ist nützlich, wenn dasthresholdeine Liste von Zahlen ist.target:elementwo eine Überlappung mit demrootfestgestellt wurde.
Wie können wir also ein Element erkennen, das gerade dabei ist, sichtbar zu werden, und ihm eine Klasse geben, damit es einfadet, sobald es in den Viewport kommt? Ungefähr so:
const callback = (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.toggle('blubb-visible'); } });}
const observer = newIntersectionObserver(callback, { threshold: 0, rootMargin: '-100px 0px -100px 0px'});
const elementsAboutToBeVisible = [...document.querySelectorAll('.blubbblubb')];elementsAboutToBeVisible.forEach((element) => observer.observe(element));Zum Abschluss können wir noch die Observation des Elements stoppen, da es jetzt sichbar ist und wir nicht mehr damit interagieren müssen:
const callback = (entries, observer) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.toggle('.blubbblubb');
observer.unobserve(entry.target); } });}Die unobserve-Methode
Section titled “Die unobserve-Methode”Diese Methode ist das Gegenteil von observe. Das bedeutet, wir können aufhören, ein Element zu tracken.