Bilder nachladen um die initiale Ladezeit zu verkürzen

Fast jeder Entwickler oder Webseiten-Betreiber hat bereits von einem Lazy Loading gelesen oder aus Webentwickler-Kreisen gehört. In diesem Beitrag erkläre ich dir, welche Vorteile es hat Bilder nachzuladen und wie du das Script einfach auf deiner Webseite hinzufügen kannst.

Bitte beachte das Update vom 26.08.2019 am Ende des Beitrags.

Was ist "Lazy Loading"?

Es gibt viele Arten des Lazy-Loadings, wie zum Beispiel das Nachladen von JavaScript in WordPress. Es können auch Schriftarten, Icons oder andere Ressourcen auf der Webseite nachgeladen werden, um die Seite schneller aufrufbar zu machen. Das macht vor allem für mobile Besucher deiner Webseite Sinn, die mit einer niedrigeren Bandbreite surfen. Natürlich können Bilder erst nach einer kurzen Zeit angesehen werden, aber zumindest können die Texte schon einmal gelesen werden. Das wird vor allem von Google belohnt, weil der Besucher deine Seite seltener direkt wieder verlassen wird.

Bilder auf der Webseite nachladen lassen

Vielleicht hast du das Lazy Loading bereits auf der Startseite von Codepalm oder in der Beitrags-Übersicht gesehen. Bilder erscheinen erst nach einer sehr kurzen Zeit mit einem "Fading"-Effekt. Sollte das Bild bereits im Cache des Browsers liegen, wird der Fading-Effekt immer noch ausgeführt, jedoch erscheinen die Bilder wesentlich schneller.

Lass uns direkt in die Integration des Skripts einsteigen, damit sich die Performance deiner Webseite verbessert.

Benutzer-Erlebnis bei deaktiviertem JavaScript

Ein Hinweis vorweg: Das Lazy-Loading Script funktioniert nur für Benutzer, die JavaScript aktiviert haben. Sollte ein Benutzer einen Browser mit deaktiverten JavaScript verwenden, werden die Bilder gar nicht mehr ausgeliefert. Da die meisten Webseiten aber JavaScript dringend benötigen, empfehle ich dir einen Hinweis für Benutzer ausgeben zu lassen, der erklärt, dass es aktiviert werden muss, um das beste Benutzer-Erlebnis für die Webseite zu garantieren. Mittlerweile ist die Verwendung von JavaScript so weit verbreitet, dass es nur noch einen verschwindend geringen Teil von Benutzern gibt, die JavaScript deaktivert haben.

Das JavaScript für das Lazy-Loading

Vorlage für das Lazy-Loading-Skripts

Die Vorlage des Skripts habe ich von Dean Hume, einen englisch-sprachigen Programmierer, der unter anderem an der Frostbite-Engine bei EA arbeitet. Er ist ein sehr passionierter Entwickler und hat bereits ein paar Lektüren über das Web geschrieben. Das Lazy Loading Script von Dean Hume ist eher Kleinteilig beschrieben. Auf seiner Webseite erhältst du weitere Informationen zum Skript.

Das Lazy-Loading JavaScript

Zusammengefasst sieht das Skript so aus:

const images = document.querySelectorAll( '.lazy-load' );
const config = {
	rootMargin: '50px 0px',
	threshold: 0.01
};

let imageCount = images.length;
let observer;

if( !( 'IntersectionObserver' in window ) ) {
  loadImagesImmediately( images );
}
else {
	observer = new IntersectionObserver( onIntersection, config );
	for (let i = 0; i < images.length; i++) { 
		let image = images[i];
		if( image.classList.contains( 'js-lazy-image--handled' ) )
			continue;
		observer.observe( image );
	}
}

function fetchImage( url ) {
	return new Promise((resolve, reject) => {
		const image = new Image();
		image.src = url;
		image.onload = resolve;
		image.onerror = reject;
	});
}

function preloadImage( image ) {
	const src = image.dataset.src;
	if( !src )
		return;
	return fetchImage( src ).then(() => { applyImage( image, src ); });
}

function loadImagesImmediately( images ) {
	for( let i=0; i < images.length; i++ ) { 
		let image = images[i];
		preloadImage( image );
	}
}

function disconnect() {
	if( !observer )
		return;
	observer.disconnect();
}

function onIntersection( entries ) {
	if( imageCount === 0 ) {
		disconnect();
		return;
	}
	
	for( let i=0; i < entries.length; i++ ) {
		let entry = entries[i];
		if( entry.intersectionRatio > 0 ) {
			imageCount--;
			observer.unobserve( entry.target );
			preloadImage( entry.target );
		}
	}
}

function applyImage( img, src ) {
	img.classList.add( 'js-lazy-image--handled' );
	img.src = src;
	img.classList.add( 'fade-in' );
}

Dieses JavaScript kannst du kopieren und in deine Webseite einfügen. Im besten Fall erstellst du dir eine eigene JS-Datei und fügst es in jeder Einzelseite deiner Webseite ein. Die Implementierung funktioniert am besten über einen globalen Header oder Footer.

Ein Hinweis zur Webseite von Dean Hume oder diesem Beitrag wäre nett, ist aber nicht notwendig.

Nachzuladende Bilder platzieren

Durch das JavaScript erhält man die Möglichkeit Bilder nachzuladen. Nun müssen wir den einzelnen Bildern noch beibringen, dass sie nachgeladen werden sollen. Das geschieht durch die Angabe der Klasse "lazy-load" im <img>-Tag.

Ein Transparentes Pixel als Platzhalter erstellen

Ein kleines Problemchen, das ich beim Script von Dean Hume entdeckt habe, ist das fehlende "src"-Attribut. Da der Wert aus dem "data-src"-Attribut in die "src" geladen wird, sobald der Besucher das Bild in den sichtbaren Bereich verschiebt, wird das Bild kurzzeitig als "fehlend" markiert. Um dieses Problem zu vermeiden, habe ich ein 1x1 Pixel großes, transparentes PNG-Bild in das Base64-Format gebracht. Diese Ressource kann in das "src"-Attribut hinzugefügt werden, damit das Bild nicht als "Broken Image" dargestellt wird.

Das ist der Code, um ein transparentes Pixel als src zu platzieren, ohne das Bild irgendwo auf dem Server gesichert zu haben:

data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABBJREFUeNpi+P//PwNAgAEACPwC/tuiTRYAAAAASUVORK5CYII=

In einem anderen Beitrag zeige ich, wie du Broken Image Elemente grafisch verbesserst. Das wäre eine Alternative zur Verwendung des transparenten Base64-Pixels.

Einfügen des Lazy-Loading Platzhalters

Das <img>-Tag wird in unserem HTML-Code nun leider etwas umfangreicher. Aber solange du beim Rendering und dem ersten Aufruf der Webseite Zeit sparst, solltest du dieses kleine Übel in Kauf nehmen. Solltest du PHP für deine Webseite verwenden, kannst du das Base64-Bild in einer globalen Variable sichern und es in jedem src-Attribut des Bilds ausgeben lassen, oder eine eigene Funktion erstellen. Das empfehle ich den fortgeschritteneren Entwicklern ;)

Das fertige <img>-Tag kannst du dir gerne kopieren und durch alle Bilder auf deiner Webseite ersetzen. Stelle sicher, dass du den Wert des Attributs "data-src" mit dem entsprechenden Ressourcen-Pfad änderst.

<img class="lazy-load" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAABBJREFUeNpi+P//PwNAgAEACPwC/tuiTRYAAAAASUVORK5CYII=" data-src="/pfad/zum/bild.jpg" />

Source-Set im Lazy-Loading integrieren

Wenn du ein wenig Know-How mit JavaScript besitzt, kannst du das Skript auch relativ erweitern, um ein ganzes "srcset" eines Bilds zu erstellen, um verschiedenartige Darstellungsgrößen des Browsers mit unterschiedlich großen Bildern abzudecken. Die einfachste Methode das zu erreichen, ist die Bearbeitung der vorletzten Zeile des JavaScript-Codes. Das img.src muss nur zu einem img.srcset geändert werden. Beachte aber, dass du damit dann jedes Bild mit einem srcset-Attribut versiehst. Die Ausgabe der "src" wird dann eliminiert. Zudem musst du darauf achten, dass das Base64-Bild in das Attribut srcset im <img>-Tag eingefügt wird, nicht mehr in das src-Attribut.

Kleine Randnotiz: Obwohl Google Pagespeed Insights die Bilder nicht in der Vorschau anzeigt, werden sie dennoch in der Google Bildersuche indexiert! Zum Glück ist Google mittlerweile Intelligent genug, um JavaScript zu verwenden :)

 

Bilder erscheinen lassen

 

Fading Effekt beim nachladen von Bildern

Eine weitere kleine Erweiterung, die ich am JavaScript vorgenommen habe, ist ein "Fade In"-Effekt. Ich möchte ungern, dass Bilder abrupt auf meiner Seite erscheinen. Durch eine kleine CSS-Erweiterung kann das Bild mit einem Fading eingeblendet werden. Die Klasse wird automatisch durch das JavaScript gesetzt, sobald ein Bild geladen und in das src-Attribut gesetzt wird.

.fade-in {
	animation-name: fadeIn;
	animation-duration: 1.3s;
	animation-timing-function: cubic-bezier(0, 0, 0.4, 1);
	animation-fill-mode: forwards;
}

@keyframes fadeIn {
  from {
	opacity: 0;
  }

  to {
	opacity: 1;
  }
}

Dieses CSS-Snippet kannst du in eine geeignete CSS-Datei einfügen, die auf jeder Seite eingebunden wird. Natürlich kannst du ein wenig mit den Werten spielen, zum Beispiel um eine schnellere Fertigstellung der Animation zu erhalten, indem du die "animation-duration" verringerst.

Wenn du dich gut mit CSS auskennst, kannst du auch deine eigenen Einblend-Animationen erstellen.

Diesen Code findest du noch einmal komplett auf Codepen:

See the Pen Lazy Loading für Bilder by Dennis Artinger (@Codepalm) on CodePen.

Bitte beachte, dass in Codepen bereits das Update vom 26.08.2019 integriert ist.

 

Performance deiner Webseite mit dem Lazy-Loading testen

Kleiner Tipp: Bevor du das Skript einbindest und die Bilder austauschst, solltest du einen Performance-Test an deiner Seite durchführen. Verwende unseren Performance-Tester, um die Leistung deiner Webseite zu testen. Sichere deine Ergebnisse und führe die Änderungen an deiner Webseite durch. Starte danach eine erneute Analyse und vergleiche die Resultate (Denke an den Server-Cache, falls du einen verwendest).

Ich wusste, dass sich die Ladezeit durch das Skript verbessert, war aber hinterher sehr positiv überrascht, wie gut das Lazy-Loading tatsächlich ist. Nachdem das Skript implementiert war, habe ich einige Millisekunden Ladezeit gespart! Die Verbesserungen sind natürlich von Webseite zu Webseite verschieden. Das liegt einerseits an der Größe der verwendeten Bildern und andererseits an der Masse der Bilder auf einer Einzelseite.

Update (26.08.2019): Erweiterung des Lazy-Loading-Scripts

In den letzten Tagen habe ich ein wenig mit dem Lazy-Loading-Skript experimentiert. Ich war mit den Ergebnissen auf Google sehr unzufrieden, da die Suchmaschine meine Bilder mit Lazy-Loading nicht erkannte.

Der neue HTML-Tag für das Lazy-Loading

Da ein transparentes Pixel im Image-Tag enthalten war, hat Google nicht gewartet, bis es geändert wurde. Ich vermute, dass die Suchmaschine das transparente Pixel als Bild verwendet und durch die mangelnde Größe nicht indexiert hatte.

Um den Fehler zu beheben habe ich das komplette Attribut "src" ersatzlos entfernt.

<img class="lazy-load" data-src="/pfad/zum/bild.jpg" />

Die Erweiterung des Lazy-Loading-Scripts

Da es nun kein transparentes Pixel mehr gab, habe ich in der Entwickler-Konsole von Google Chrome einen Fehler erhalten, wenn der Pfad zu einem Bild nicht korrekt war. Zudem erhielt ich einen "Broken Image"-Fehler, der ein unschönes Bildchen ausgab.

In meinem Beitrag Broken Image Elemente grafisch optimieren habe ich bereits eine CSS-Formatierung erstellt, die dieses hässliche Icon durch eine schöne Darstellung verwandeln kann.

Ich musste den Lazy-Loading-Code also so umschreiben, dass bei einem 404-Fehler einer Ressource das src-Attribut mit einem Leerstring oder einer Raute (#) platziert wird. Die Raute wird gerne von Webentwicklern verwendet, um einen fehlerhaften Link oder eine fehlerhafte Ressource darzustellen.

Folgende Funktionen habe ich leicht geändert:

/* ... */

function preloadImage( image ) {
	const src = image.dataset.src;
	console.log( image.dataset );
	if( !src )
		return;
	return fetchImage( src ).then(() => { applyImage( image, src ); }, () => { applyImage( image, '#' ); });
}

/* ... */

function applyImage( img, src ) {
	console.log( 'applyImage( '+img+', '+src+' )' );
	if( src != "#" ) {
		img.classList.add( 'js-lazy-image--handled' );
		img.src = src;
		img.classList.add( 'fade-in' );
	}
	else {
		img.src = src;
		img.classList.add( 'js-lazy-image--error' );
	}
}

Durch diese kleine Erweiterung kann nun einen Zustand im Hinterlegen und mit CSS zu prüfen, ob die Ressource fehlerhaft ist.

Die CSS-Erweiterung für das Lazy-Loading

Zudem habe ich eine kleine Erweiterung in der Broken-Image-Formatierung vorgenommen, um einen Lade-Zustand eines Bilds zu erhalten.

img.lazy-load:not([src]):after {
    content: "Bild '" attr(alt) "' wird geladen";
}

Den Wert der Eigenschaft "content" kannst du nach belieben ändern. Es ist auch möglich mit FontAwesome ein rotierendes Lade-Symbol zu erstellen.

 

Teile gerne deine Resultate in den Kommentaren mit mir. Ich bin gespannt, ob du auch so gute Verbesserungen feststellen kannst wie andere.

 

Codepalm
Lazy Loading - Bilder nachladen lassen