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 Dateien, Videos, iframes erst zu laden, wenn sie in den sichtbaren Sektor des ViewPorts kommen.

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

18-12-15 SITEMAP CSS HTML JS Basis JS Web Tutorial SVG

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.

Animierte Grafik Intersection Observer
Elemente und Animationen laden, wenn sie in den sichtbaren Bereich des Browserfensters kommen

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 breiter Basis von allen Browsern unterstützt wurde.

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  ------+
                                        |
const io = new IntersectionObserver (handleEntries, options);
                                                     |
                                     Optionen  ------+

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 und rootMargin

Intersection Observer rootMargin=0-threshold
rootMargin=0 / threshold=1
Das Element wird geladen, wenn es 100% im sichtbaren Ausschnitt ist
Intersection Observer rootMargin=0-threshold
rootMargin=50 / threshold=0
Das Element wird geladen, sobald es den Abstand rootMargin="50" betritt

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.

Intersection Observer rootMargin=0-threshold
rootMargin=0 / threshold=0.5
Callback-Funktion aufrufen, wenn das Element zu 50% im sichtbaren Ausschnitt ist
Intersection Observer rootMargin=0-threshold
rootMargin=0 / threshold=0.5
Callback-Funktion aufrufen, wenn das Element den sichtbaren Ausschnitt zu 50% verlassen hat

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

margin="0" / threshold="[0, 0.25, 0.5, 0.75, 1]"

CSS Transition und Transform Scale beim Scrollen

Mit der Feststellung, dass ein Element im Browserfenster sichtbar wird, startet eine Animation.

CSS Transition und Transform Scale beim Scrollen

Dafür etwas CSS

CSS transition und transform scale
.jade {
	background: url('marienkaefer-720.webp') no-repeat;
	background-size: cover;
	background-position: center center;
	transition: transform 5s ease;
	transform: scale(1);
}

.jade-scale.image-scaling {
	transform: scale(1.8);
}
<div class="image-container">
	<div class="jade-scale">
	</div>
	<div class="labelText">
		CSS Transition und Transform Scale beim Scrollen
	</div>
</div>
IntersectionObserver – Script
const jadeScale = document.querySelectorAll(".jade-scale");

const jadeScaleObserver = new IntersectionObserver (function (entries, observer) {
	entries.forEach(function(entry) {
		if (entry.isIntersecting) {
			entry.target.classList.add("image-scaling");
		} else {
			entry.target.classList.remove("image-scaling");
		}
	}, {threshold: 1});
});

jadeScale.forEach ( function (jadeScale) {
	jadeScaleObserver.observe (jadeScale);
});

Die Option {threshold: 1} sorgt dafür, dass die Animation erst startet, wenn das Bild vollständig im ViewPort ist.

Hintergrundbilder: Lazy Loading per Intersection Observer

Im einfachsten Fall – Bilder nachladen, wenn sie in den Viewport kommen –, braucht der Intersection Observer nur wenige Zeilen. Die Elemente der Slideshow haben eine zusätzliche CSS-Klasse lazy, das die CSS-Eigenschaft background-image: none setzt.

.lazy.slide.slide1,
.lazy.slide.slide2,
.lazy.slide.slide3,
.lazy.slide.slide4 { background-image: none}

.slide.slide1.inview {
	background: url(flowers--04.webp);
}
.slide.slide2.inview  {
	background: url(flowers--03.webp);
}
.slide.slide3.inview  {
	background: url(flowers--02.webp);
}
.slide.slide4.inview  {
	background: url(flowers--01.webp);
}

Wenn die Slideshow in den sichtbaren Ausschnitt des Browserfensters kommt, fügt das Script die Klasse inview hinzu, um die Hintergrundbilder zu laden.

Slide 1
Slide 2
Slide 3
Slide 4
Slide 1

Heute unterstützen alle modernen Browser das loading lazy-Attribut und der Intersection Observer wird für das Nachladen von Bildern nicht mehr gebraucht. Für Hintergrundbilder besteht diese einfache Möglichkeit nicht.

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 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.

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

Script

const lazyBg = document.querySelectorAll(".slide");

const lazyBackgroundObserver = new IntersectionObserver (function (entries, observer) {
	entries.forEach(function(entry) {
		if (entry.isIntersecting) {
			entry.target.classList.add("inview");
		}
	},{});
});

lazyBg.forEach ( function (lazyBackground) {
	lazyBackgroundObserver.observe(lazyBackground);
});

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

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.

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

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];
	if (observed.isIntersecting) {
		document.querySelector(".crossing").classList.add("swimming");
	}
}

const observer = new IntersectionObserver (callback, options);

if (animateTurtle) {
	observer.observe (animateTurtle);
}
Schildkröte crossing

unobserve

Wenn das observierte Element nicht länger beobachtet werden soll – z.B. die Animation nach Ablauf nicht erneut starten soll oder Dateien nicht erneut geladen werden sollen, beendet die Methode unobserve den Intersection Observer.

if (entry.isIntersecting) {
	loadFile (entry)
	observer.unobserve(entry.target)
}

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 IE

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.

resizeObserver

Neben dem intersectionObserver gibt es ein weiteres Application Interface – resizeObserver.

Es war einmal eine kluge junge Ente, die hervorragend sehen konnte und obendrein eine angesehene Mathematikerin war. Durch das lange Studieren bei schlechtem Licht wurden ihre Augen mit den Jahren zusehens schlechter, und eines Tages konnte sie kaum noch ihre eigenen Randnotizen erkennen, nicht einmal ihre Lieblingsformel e = N*te.

Formeln mit MathML konnte sie nur noch erkennen, wenn sie nicht innerhalb von Zeilen standen, sondern zentral mit viel Padding drum herum. k= n m a (k)

Weil Chrome MathML nicht verstand, konnte sie Publikationen nur noch mit Firefox studieren. Darum bat sie eines Tages ihren Assistenten, ihr einen guten Augenarzt zu suchen.

width: 200px
const myObserver = new ResizeObserver(entries => {
	entries.forEach(entry => {
		document.querySelector (".res").innerHTML = "width: " + entry.contentRect.width + "
height: " + entry.contentRect.height; }); }); const someEl = document.querySelector('.ente'); myObserver.observe(someEl);