Intersection Observer

Lazy Loading Javascript Intersection Observer

Das Intersection Observer API beobachtet, ob (isIntersecting) und wie weit (Intersection Ratio) sich ein Element mit dem ViewPort überschneidet, um Animationen zu starten oder Bilder, Videos, iframes erst zu laden, wenn sie in den sichtbaren Sektor des ViewPorts kommen.

So ist die Seite schneller aufgebaut und Datentransfer, der u.U. nicht benötigt wird, findet gar nicht erst statt.

18-12-15 SITEMAP

Scrollen und Rechnen: getBoundingClientRect

Der alte Mechanismus hinter dem Lazy Loading mit getBoundingClientRect ist alt, solide und kompatibel über die meisten Browser. Dabei wird laufend die Scroll-Position mit dem Abstand des Elements von der Grenze des sichtbaren Viewports verglichen. Diese permanenten Abfragen des DOM sind eine Last, die schnell zu Ruckeln und Hakeln während des Scrollens führt.

Der Intersection Observer beobachtet Änderungen eines Elements im Verhältnis zu einem Eltern-Element – meist zum Viewport. Wie weit überschneidet sich das Element mit einem umgebenden Element? Die Informationen über die Änderungen wurden vor allem für das Nachladen von Inhalten beim Scrollen (z.B. lazy loading images) benutzt, bis das loading-Attribut für das img-Tag auf den Plan stand. Mit der allgemeinen Feststellung, dass ein Element in den Viewport kommt, lässt sich aber auch eine Animation starten, wenn sie in den ViewPort kommt.

Intersection Observer

Der Aufruf new IntersectionObserver hat zwei Argumente: Das erste ist eine Callback-Funktion, die ausgeführt wird, sobald ein Element in den Viewport kommt oder wenn sich der Abstand zwischen Elementen um einen gewissen Betrag geändert hat. Damit muss die Position eines Elements in Hinsicht auf ein anderes Element beim Scrollen nicht mehr permanent abgefragt werden.

Das zweite Argument listet die Optionen für den Intersection Observer.

               callback function  ------+
                                        |
var io = new IntersectionObserver (handleEntries, options);
                                                     |
                                     Optionen  ------+
scene2-l
scene3-s
scene4-s
scene6-s
scene7-s
scene10-s

Im einfachsten Fall – Bilder nachladen, wenn sie in den Viewport kommen –, braucht der Intersection Observer nur wenige Zeilen. Die img-Tags haben eine zusätzliche CSS-Klasse lazy. Anstelle der URL sitzt im src-Attribut ein Platzhalter und die URL ist im data-Attribut notiert.

Der Platzhalter und die Angabe von width und height sind wichtig für ein stabiles Layout: Schwappt das Bild nachträglich in die Seite, käme es sonst zu einem Cumulative Layout Shift (CLS) – das Layout verschiebt sich. Ärgerlich, wenn man gerade einen Text liest und noch ärgerlicher, wenn jetzt ein Klick zu einem falschen Link führt.

HTML

<img class="lazy" src="blank.png" data-src="img.jpg" width="480" height="480" alt="scene2-l">

Script

var lazyloadImages;    

lazyloadImages = document.querySelectorAll(".lazy");
var imageObserver = new IntersectionObserver (function (entries, observer) {
	entries.forEach(function(entry) {
		if (entry.isIntersecting) {
			var image = entry.target;
			image.src = image.dataset.src;
			image.classList.remove("lazy");
			imageObserver.unobserve(image);
		}
	});
});

lazyloadImages.forEach(function(image) {
	imageObserver.observe(image);
});

Die Optionen in diesem Beispiel sind leer: Per Voreinstellung gilt der Viewport als Elternelement. Das Bild wird angezeigt, wenn es im Viewport sichtbar ist.

Intersection Observer Options

Mit den Default-Optionen des IntersectionObserver wird die Callback-Funktion aufgerufen, wenn ein Element teilweise in den Viewport kommt und den ViewPort verläßt. Das Observer API arbeitet nicht mit einem Pixelwert für das Überschneiden der Elemente, sondern reagiert, wenn die Überschneidung so irgendwie bei dem angegebenen Prozentsatz des threshold liegt.

root
Das Elternelement oder der Viewport, in dem das Element liegt. null steht für den Viewport des Browsers.
threshold
ist ein Wert oder ein Array von Werten zwischen 0 und 1, bei denen der Intersection Observer die Callback-Funktion ausführt.
rootMargin
Beim Umfang des Überscheidungsbereichs (root) wird ein Abstand eingerechnet, ähnlich dem Margin bei CSS.

threshold = 0.5 würde beim Betreten und beim Verlassen des Root-Elements die Callback-Funktion aufrufen. Das könnte z.B. der Ablauf für ein Video sein, das bei Erscheinen im Viewport anläuft und pausiert, wenn es den Viewport verläßt.

RootRootRootRoot

Ist threshold ein Array – z.B. threshold: [0, 0.25, 0.5, 0.75, 1] –, wird die Callback-Funktion bei jedem der Werte aufgerufen.

Informationen des Intersection Observer

Ein Blick in die Konsole zeigt gleich beim Laden der Seite, dass sich das Element noch nicht mit dem root-Element überschneidet (intersectionRatio: 0) und isIntersecting steht noch auf false.

boundingClientRect: DOMRectReadOnly {x: 369, y: 55.0625, width: 900, height: 622, top: 55.0625, …}
intersectionRatio: 0
intersectionRect: DOMRectReadOnly {x: 369, y: 55.0625, width: 900, height: 621.9375, top: 55.0625, …}
isIntersecting: false
rootBounds: DOMRectReadOnly {x: 0, y: 0, width: 2019, height: 677, top: 0, …}
target: <div class="simple">
time: 22273

Sobald das Element in den sichtbaren Ausschnitt des Browserfensters kommt, ist intersectionRatio auf 1 gesetzt und isIntersecting true.

boundingClientRect: DOMRectReadOnly {x: 369, y: 51.0625, width: 900, height: 622, top: 51.0625, …}
intersectionRatio: 1
intersectionRect: DOMRectReadOnly {x: 369, y: 51.0625, width: 900, height: 622, top: 51.0625, …}
isIntersecting: true
rootBounds: DOMRectReadOnly {x: 0, y: 0, width: 2019, height: 677, top: 0, …}
target: <div class="simple">
time: 31279

Mehrere Bilder laden, Lazy Loading responsive

Wenn mehrere Elemente beobachtet werden sollen, sollten nach Möglichkeit alle Elemente vom selben IntersectionObserver durch mehrfache Aufrufe von observer () überwacht werden.

Bilder, die over the fold beim Laden der Seite angezeigt werden, sollten natürlich nicht per Observer geladen werden.

Wenn Bilder responsive mit HTML srcset eingebunden werden, kann die Callback-Funktion im IntersectionObserver die matchMedia-Abfrage einsetzen.

Animation mit Intersection Observer

Das Lazy Loading von Bildern, iframes und Video wird von Chrome, Edge, Firefox und Opera bereits durch ein einfaches HTML-Attribut eingesetzt: loading="lazy". Der IntersectionObserver lädt nicht nur Bilder nach, sondern startet auch Animationen, wenn sie in den ViewPort kommen. Mal mit formeller Callback-Funktion und Optionen:

HTML und Optionen des Intersection Observers

<div class="turtle">
	<div class="crossing"> … </div>
</div>
…
let animateTurtle = document.querySelector(".turtle");

const options = {
	root: null,
	rootMargin: "0px",
	threshold: 0.7,
};

Wenn das Element turtle in den Viewport kommt, setzt die Callback-Funktion eine zusätzliche Klasse swimming ein.

Callback-Funktion und Aufruf des Intersection Observers

const callback = function (entries, observer) {
	let observed = entries[0];
	console.log (observed);
	if (observed.isIntersecting) {
		document.querySelector(".crossing").classList.add("swimming");
		rotateLegs();
	}
}

const observer = new IntersectionObserver (callback, options);

if (animateTurtle) {
	observer.observe (animateTurtle)
}

Relativ und fixed positionierte Intersection-Roots

Wenn Target-Elemente absolut innerhalb eines relativ positionierten Elements liegen, meldet der Intersection Observer keinen Treffer, wenn das Target-Element den relativ positionierten Root überschneidet.

Das gilt wohl auch für Target-Element mit position:fixed (?).

Browser-Support für IO

Zwar sind Browser ohne Javascript zu einer Seltenheit geworden, aber wenn sie dennoch beachtet werden sollen, kann z.B. ein noscript-Tag eingesetzt werden.

Die Abfrage, ob der IntersectionObserver im Window-Objekt vorhanden ist,

if (IntersectionObserver in window) {

}

hilft älteren Browsern, insbesondere IE, der das Intersection Observer-API nicht unterstützt. Falls nicht, lädt das Script die Merkmale direkt.