Sie kennen das Konzept des Lazy Loadings für Bilder. Es verhindert, dass der Browser Bilder lädt, bis diese Bilder im (oder fast im) Ansichtsfenster des Browsers erscheinen.
Es gibt eine Fülle von JavaScript-basierten Lazy-Loading-Lösungen. GitHub hat über 3.400 verschiedene Lazy-Load-Repos, und das sind nur die, bei denen "lazy load" in einer durchsuchbaren Zeichenkette vorkommt! Die meisten davon basieren auf demselben Trick: Anstatt die URL eines Bildes im src-Attribut zu platzieren, platziert man sie in data-src – was dasselbe Muster für responsive Bilder ist.
- JavaScript beobachtet, wie der Benutzer die Seite nach unten scrollt.
- Wenn der Benutzer auf ein Bild stößt, verschiebt JavaScript den Wert von
data-srcinsrc, wo er hingehört. - Der Browser fordert das Bild an und es wird angezeigt.
Das Ergebnis ist, dass der Browser weniger Bilder im Voraus lädt, sodass die Seite schneller geladen wird. Darüber hinaus, wenn der Benutzer nie weit genug scrollt, um ein Bild zu sehen, wird dieses Bild nie geladen. Das bedeutet schnellere Seitenladezeiten und weniger Daten, die der Benutzer ausgeben muss.
„Das ist erstaunlich!“ denken Sie vielleicht. Und Sie haben Recht… es ist erstaunlich!
Das gesagt, es führt tatsächlich zu einem bemerkenswerten Problem: Bilder, die das src-Attribut nicht enthalten (auch wenn es leer oder ungültig ist), haben keine Höhe. Das bedeutet, dass sie bis zum Lazy-Loading nicht die richtige Größe im Seitenlayout haben.
Update! Die Zeiten haben sich hier schnell geändert, und um Lazy-Load-Ruckler zu vermeiden, müssen Sie lediglich die korrekten nativen Breiten- und Höhenattribute für Bilder angeben, und sie werden schön geladen, auch wenn CSS das Bild flüssig macht. Machen Sie es also so: <img src="bild.jpg" width="800" height="600">
Wenn ein Benutzer scrollt und Bilder per Lazy-Loading geladen werden, gehen diese img-Elemente von einer Höhe von 0 Pixeln auf die benötigte Höhe über. Dies verursacht **Reflow**, bei dem der Inhalt unter oder um das Bild herum verschoben wird, um Platz für das frisch geladene Bild zu schaffen. Reflow ist ein Problem, da es sich um eine benutzerblockierende Operation handelt. Es verlangsamt den Browser, indem es ihn zwingt, das Layout aller Elemente neu zu berechnen, die von der Form dieses Bildes betroffen sind. Die CSS-Eigenschaft scroll-behavior könnte hier irgendwann helfen, aber ihre Unterstützung muss sich verbessern, bevor sie eine praktikable Option ist.
Lazy Loading garantiert nicht, dass das Bild vollständig geladen wird, bevor es in den Viewport gelangt. Das Ergebnis ist eine gefühlt ruckelige Erfahrung, auch wenn es ein großer Leistungsgewinn ist.
Es gibt weitere Probleme mit dem Lazy Loading von Bildern, die erwähnenswert sind, aber außerhalb des Rahmens dieses Beitrags liegen. Wenn beispielsweise JavaScript überhaupt nicht ausgeführt wird, werden keine Bilder auf der Seite geladen. Das ist eine häufige Sorge für jede JavaScript-basierte Lösung, aber dieser Artikel befasst sich nur mit der Lösung der Probleme, die durch Reflow entstehen.
Wenn wir vorab geladene Bilder zwingen könnten, ihre normale Breite und Höhe (d. h. ihr Seitenverhältnis) beizubehalten, könnten wir Reflow-Probleme vermeiden und sie trotzdem per Lazy-Loading laden. Dies ist etwas, das ich kürzlich beim Erstellen einer Progressive Web App bei DockYard, wo ich arbeite, lösen musste.
Für zukünftige Referenz gibt es ein HTML-Attribut namens intrinsicsize, das dazu dient, das Seitenverhältnis beizubehalten, aber derzeit ist dies in Chrome nur experimentell.
Hier ist, wie wir es gemacht haben.
Seitenverhältnis beibehalten
Es gibt viele Möglichkeiten, wie wir Seitenverhältnisse beibehalten können. Chris hat einmal eine erschöpfende Liste von Optionen zusammengestellt, aber hier sind die bildspezifischen Optionen, die wir uns ansehen.
Das Bild selbst
Der Bild-src liefert ein natürliches Seitenverhältnis. Selbst wenn ein Bild responsiv skaliert wird, gelten seine natürlichen Abmessungen immer noch. Hier ist ein ziemlich gängiger Teil von responsivem Bild-CSS
img {
max-width: 100%;
height: auto;
}
Dieses CSS besagt, dass Bilder die Breite des Elements, das sie enthält, nicht überschreiten sollen, aber die Höhe richtig skalieren, sodass es kein „Dehnen“ oder „Stauchen“ gibt, wenn das Bild skaliert wird. Selbst wenn das Bild Inline-height- und width-Attribute hat, werden sie diese auf kleinen Viewports weiterhin schön verhalten lassen.
Dieses Verhalten des „natürlichen Seitenverhältnisses“ bricht jedoch zusammen, wenn noch kein src vorhanden ist. Browser kümmern sich nicht um data-src und tun nichts damit, sodass es keine wirklich praktikable Lösung für Lazy-Loading-Reflow ist, aber es ist wichtig, um die „normale“ Art und Weise zu verstehen, wie Bilder einmal geladen werden.
Ein Pseudo-Element
Viele Entwickler – mich eingeschlossen – waren frustriert, als sie versuchten, Pseudo-Elemente (z. B. ::before und ::after) zu verwenden, um img-Elementen Dekorationen hinzuzufügen. Browser rendern keine Pseudo-Elemente von Bildern, da img ein ersetztes Element ist, was bedeutet, dass sein Layout von einer externen Ressource gesteuert wird.
Es gibt jedoch eine Ausnahme von dieser Regel: Wenn das src-Attribut eines Bildes ungültig ist, rendern Browser seine Pseudo-Elemente. Wenn wir also das src für ein Bild in data-src speichern und das src leer ist, können wir ein Pseudo-Element verwenden, um ein Seitenverhältnis festzulegen.
[data-src]::before {
content: '';
display: block;
padding-top: 56.25%;
}
Das setzt ein Seitenverhältnis von 16:9 auf ::before für jedes Element mit einem data-src-Attribut. Sobald data-src zum src wird, stoppt der Browser das Rendern von ::before und das natürliche Seitenverhältnis des Bildes übernimmt.
Hier ist eine Demo
Siehe den Pen Image Aspect Ratio: ::before padding von James Steinbach (@jdsteinbach) auf CodePen.
Diese Lösung hat jedoch ein paar Nachteile. Erstens beruht sie auf der Zusammenarbeit von CSS und HTML. Ihre Stylesheet muss eine Deklaration für jedes Bildseitenverhältnis enthalten, das Sie unterstützen müssen. Es wäre viel besser, wenn die Vorlage ein Bild einfügen könnte, *ohne* CSS-Änderungen zu benötigen.
Zweitens funktioniert es zum Zeitpunkt der Erstellung nicht in Safari 12 und darunter oder Edge. Das ist ein ziemlich großer Verkehrsanteil, um schlechte Layouts zu verschicken. Um fair zu sein, die Aufrechterhaltung des Seitenverhältnisses ist eine Art progressive Verbesserung – an der endgültig gerenderten Seite ist nichts „kaputt“. Dennoch ist es viel besser, das Reflow-Problem zu lösen und dass Bilder wie erwartet gerendert werden.
Data URI (Base64) PNGs
Ein weiterer Versuch, das Seitenverhältnis zu erhalten, war das Einbetten von Data URIs für den src als PNG. Mit png-pixel.com wird die gesamte Base64-Codierung mit beliebigen Dimensionen und Farben erleichtert. Dies kann direkt in das src-Attribut des Bildes im HTML eingefügt werden.
<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAQAAAA3fa6RAAAADklEQVR42mNkAANGCAUAACMAA2w/AMgAAAAASUVORK5CYII=" data-src="//picsum.photos/900/600" alt="Lazy loading test image" />
Das Inline-PNG hat ein Seitenverhältnis von 3:2 (dasselbe Seitenverhältnis wie das endgültige Bild). Wenn src durch den Wert von data-src ersetzt wird, behält das Bild sein Seitenverhältnis genau so bei, wie wir es wollen!
Hier ist eine Demo
Siehe den Pen Image Aspect Ratio: inline base64 PNG von James Steinbach (@jdsteinbach) auf CodePen.
Und ja, dieser Ansatz hat auch einige Nachteile. Obwohl die Browserunterstützung viel besser ist, ist sie kompliziert zu warten. Wir müssen eine Base64-Zeichenkette für jede neue Bildgröße generieren und dieses Objekt von Zeichenketten dann für jedes verwendete Templating-Tool verfügbar machen. Es ist auch nicht die effizienteste Methode, diese Daten darzustellen.
Ich habe weiter erforscht und einen kleineren Weg gefunden.
SVG mit Base64 kombinieren
Nachdem ich die Inline-PNG-Option untersucht hatte, fragte ich mich, ob SVG ein kleineres Format für Inline-Bilder sein könnte, und hier ist, was ich herausgefunden habe: Ein SVG mit einer viewBox-Deklaration ist ein Platzhalterbild mit einem leicht editierbaren nativen Seitenverhältnis.
Zuerst habe ich versucht, ein SVG Base64-zu-codieren. Hier ist ein Beispiel dafür, wie das in meinem HTML aussah.
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHZpZXdCb3g9JzAgMCAzIDInPjwvc3ZnPg==" data-src="//picsum.photos/900/600" alt="Lazy loading test image">
Bei kleinen, einfachen Seitenverhältnissen ist dies ungefähr gleich groß wie die Base64-PNGs. Ein Verhältnis von 1:1 wäre 114 Bytes mit Base64-PNG und 106 Bytes mit Base64-SVG. Ein Verhältnis von 2:3 beträgt 118 Bytes mit Base64-PNG und 106 Bytes mit Base64-SVG.
Bei größeren, komplexeren Verhältnissen bleiben die Base64-SVGs jedoch klein, was ein echter Vorteil bei der Dateigröße ist. Ein Verhältnis von 16:9 beträgt 122 Bytes in Base64-PNG und 110 Bytes in Base64-SVG. Ein Verhältnis von 923:742 beträgt 3.100 Bytes in Base64-PNG, aber nur 114 Bytes in Base64-SVG! (Das ist kein gängiges Seitenverhältnis, aber ich musste es für den Anwendungsfall meines Kunden mit benutzerdefinierten Abmessungen testen.)
Hier ist eine Tabelle, um diese Vergleiche klarer zu sehen.
| Seitenverhältnis | base64 PNG | base64 SVG |
|---|---|---|
| 1:1 | 114 Bytes | 106 Bytes |
| 2:3 | 118 Bytes | 106 Bytes |
| 16:9 | 122 Bytes | 110 Bytes |
| 923:742 | 3.100 Bytes | 114 Bytes |
Die Unterschiede sind bei einfachen Verhältnissen vernachlässigbar, aber Sie sehen, wie gut sich SVG skaliert, wenn die Verhältnisse komplexer werden.
Wir haben jetzt eine viel bessere Browserunterstützung. Diese Technik wird von allen großen Anbietern unterstützt, einschließlich Chrome, Firefox, Safari, Opera, IE11 und Edge, hat aber auch großartige Unterstützung in mobilen Browsern, einschließlich Safari iOS, Chrome für Android und Samsung für Android (ab Version 4.4).
Hier ist eine Demo
Siehe den Pen Image Aspect Ratio: inline base64 SVG von James Steinbach (@jdsteinbach) auf CodePen.
🏆 Wir haben einen Gewinner!
Ja, das haben wir, aber bleiben Sie bei mir, während wir diesen Ansatz noch weiter verbessern! Ich erinnerte mich daran, dass Chris vorschlug, dass wir keine Base64-Codierung mit in CSS-Hintergrundbildern eingebetteten SVGs verwenden sollten und dachte, dieser Rat könnte auch hier zutreffen.
In diesem Fall habe ich anstelle der Base64-Codierung der SVGs die „Optimized URL-encoded“-Technik aus diesem Beitrag verwendet. Hier ist die Markierung.
<img src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 3 2'%3E%3C/svg%3E" data-src="//picsum.photos/900/600" alt="Lazy loading test image" />
Das ist nur geringfügig kleiner als Base64-SVG. Das 1:1 ergibt 106 Bytes in Base64 und 92 Bytes bei URL-Codierung. 16:9 ergibt 110 Bytes in Base64 und 97 Bytes bei URL-Codierung.
Wenn Sie mehr über die Datengröße nach Datei und Codierungsformat erfahren möchten, vergleicht diese Demo verschiedene Byte-Größen zwischen all diesen Techniken.
Die *wirklichen* Vorteile, die das URL-codierte SVG zu einem klaren Gewinner machen, sind jedoch, dass sein Format menschenlesbar, einfach zu templaten und unendlich anpassbar ist!
Sie müssen keinen CSS-Block erstellen oder eine Base64-Zeichenkette generieren, um einen perfekten Platzhalter für Bilder zu erhalten, deren Abmessungen unbekannt sind! Hier ist zum Beispiel eine kleine React-Komponente, die diese Technik verwendet.
const placeholderSrc = (width, height) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`
const lazyImage = ({url, width, height, alt}) => {
return (
<img
src={placeholderSrc(width, height)}
data-src={url}
alt={alt} />
)
}
Siehe den Pen React LazyLoad Image with Stable Aspect Ratio von James Steinbach (@jdsteinbach) auf CodePen.
Oder, wenn Sie Vue bevorzugen.
Siehe den Pen Vue LazyLoad Image with Stable Aspect Ratio von James Steinbach (@jdsteinbach) auf CodePen.
Ich freue mich, berichten zu können, dass sich die Browserunterstützung mit dieser Verbesserung nicht geändert hat – wir haben immer noch die volle Unterstützung wie bei Base64-SVG!
Fazit
Wir haben mehrere Techniken untersucht, um Inhaltsverschiebungen zu verhindern, indem wir das Seitenverhältnis eines per Lazy-Loading geladenen Bildes beibehalten, bevor der Austausch stattfindet. Die beste Technik, die ich finden konnte, ist ein eingebettetes und optimiertes URL-codiertes SVG mit in der viewBox-Attribute definierten Bildabmessungen. Dies kann mit einer Funktion wie dieser geskriptet werden.
const placeholderSrc = (width, height) => `data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${width} ${height}"%3E%3C/svg%3E`
Es gibt mehrere Vorteile dieser Technik.
- Solide Browserunterstützung auf Desktop und Mobilgeräten.
- Kleinste Byte-Größe.
- Menschenlesbares Format.
- Einfach zu templaten ohne Laufzeit-Codierungsaufrufe.
- Unendlich erweiterbar.
Was halten Sie von diesem Ansatz? Haben Sie etwas Ähnliches verwendet oder haben Sie eine völlig andere Methode zur Behandlung von Reflow? Lassen Sie es mich wissen!
Persönlich verwende ich eine Hülle um mein Bild mit einer „Utility“-Klasse, die ich schreibe (funktionale CSS).
Etwas wie das
Und meine definierte Utility-Klasse
„fluid-holder“ ist eine Sass-Funktion, die den korrekten Abstand basierend auf dem von mir übergebenen Verhältnis zum Parameter setzt.
Ich verwende diese SVG-Technik seit einiger Zeit in meinem WordPress-Blog für GIFs: Ich verwende die generierte JPEG-Miniaturansicht als Hintergrundbild für das
<img>und platziere die URL des GIFs in einemdata-gif-url-Attribut. Dassrc-Attribut mit der Data-URI für das SVG wird im laufenden Betrieb generiert, wenn ich es in meine Blogbeiträge einfüge (mit einem kleinen benutzerdefinierten Gutenberg-Block, der die Attribute der Datei liest). Dann überlagere ich einfach eine Schaltfläche, die die Attributesrcunddata-gif-urlumschaltet, und das war's!Das macht es viel einfacher, als jedes GIF auf ein bestimmtes Seitenverhältnis zuschneiden zu müssen. (und ja, ich weiß, dass es schlecht ist, GIFs anstelle einer Videodatei zu verwenden… dafür werde ich später eine Lösung finden)
Sie können den finalen Ausschnitt sogar noch kleiner machen, indem Sie das Root-
<svg>-Element selbstschließend machen.…aber warum nicht einfach Inline-
height- undwidth-Attribute verwenden? Das ist die semantischste Lösung und funktioniert garantiert. Noch besser, es ermöglicht Ihnen, ein 1 Pixel großes transparentes PNG-Data-URL als Platzhalter zu verwenden, bis das Bild zu laden beginnt.Das scheint nicht wie beschrieben zu funktionieren: Ich habe das in einem CodePen ausprobiert & das 1px transparente PNG-Data-URI hat die Höhe/Breite-Attribute überschrieben: https://codepen.io/jdsteinbach/pen/EOzWqa
Aber warum nicht einfach Inline-
height- undwidth-Attribute verwenden? Diese funktionieren garantiert, können pro Bild festgelegt werden, sind am semantischsten und ermöglichen die Verwendung einer 1px transparenten PNG-DataURL als Platzhalter, bis das Bild zu laden beginnt.„Selbst wenn das Bild Inline-
height- undwidth-Attribute hat, werden sie diese auf kleinen Viewports weiterhin schön verhalten lassen.“ (Der Autor bezieht sich auf die „fluid image“-Stile)Hallo,
Gibt es Auswirkungen auf SEO mit Data URI?
Ich verwende einfach JS, um den Abstand basierend auf den Breiten- und Höhenattributen festzulegen – das bedeutet, dass Clients ohne JS keinen großen leeren Whitespace-Block erhalten (und ein Fallback-Bild aus einem noscript-Tag geladen wird).
Shameless plug: https://shakyjake.github.io/Lazy-Loading-With-IntersectionObserver/
Die Verwendung von
srchat einen weiteren großen Nachteil: Sie unterdrücken das progressive Laden/Anzeigen von Bildern, was zu einer viel späteren Bildanzeige führt. Dies geschieht selbst bei nicht-progressiven Bildern und der Unterschied ist ziemlich groß. Verwenden Sie diese Technik also nicht.Alex, können Sie das genauer erläutern? Was ist der Nachteil, einen Platzhalter im
src-Attribut bereitzustellen?Was ist mit dem ``-Element, bei dem jede Quelle ein anderes Verhältnis hat?
Wurde für jede Quell-
srcauf die gleiche Weise gelöst.`picture source` akzeptiert kein
src-Attribut und wenn ich Inline-SVG alssrcsetsetze, gibt mir der Browser diesen Fehler: "Failed parsing ‘srcset’ attribute value since it has an unknown descriptor." Irgendwelche Vorschläge, wie mansrcsetals Inline-SVG setzt? Danke.Die Beibehaltung des Seitenverhältnisses ist ein wirklich guter Rat, danke für das Teilen dieses Artikels. Wenn das Ziel darin besteht, Reflows zu vermeiden, dann machen die meisten Lazy-Load-Bibliotheken es falsch, denn nur IntersectionObserver vermeidet Reflows.
Ich habe einen kleinen (Vanilla-)Ansatz, der hier beschrieben wird, vielleicht inspiriert das jemanden :-) https://medium.com/@iliketoplay/lazy-loading-a-flexible-and-performant-approach-5a46b97ef60f
Hoffentlich wird das in Zukunft durch eine bessere CSS-Eigenschaft gelöst (siehe "was nun?" in https://www.bram.us/2017/06/16/aspect-ratios-in-css-are-a-hack/).
Die im Artikel gezeigte Methode eliminiert den Reflow nicht.
srcsollte beim Ladeereignis für das Bild geändert werden.Ich habe das Reflow-Problem mit winzigen, hochkomprimierten Thumbnail-Bildern (z. B. 40×30) mit demselben Seitenverhältnis wie das größere Bild (z. B. 800×600) gelöst. Thumbnails können eingebettet werden, aber Dienste wie Cloudinary können sie im laufenden Betrieb generieren.
Das Vollbild wird dann bei Bedarf absolut darüber positioniert. (Das Vollbild wird dann zu einem Standardblock gemacht und das Thumbnail entfernt – aber das war nur, um ein gewisses ruckeliges Verhalten in Edge zu verhindern.)
Demo: https://codepen.io/craigbuckler/pen/yPqLXW
Das ist ein wirklich cleverer Trick!
Es könnte interessant sein zu wissen, dass diese Technik nicht funktioniert, wenn die Bildverhältnisse zwischen verschiedenen Bildschirmgrößen unterschiedlich sind (z. B. 1:1 auf kleinen Bildschirmen, 2:1 auf großen Bildschirmen). Der Browser sucht nur nach dem
src-Tag, um die Elementgröße zu definieren, und *optimierte URL-codierte* Bilder werden vomsrcset-Tag für responsive Bilder nicht akzeptiert.Ich könnte mich irren, aber das ist, was ich bei den wenigen Tests herausgefunden habe, die ich heute Nachmittag durchgeführt habe. Ich würde mich freuen zu sehen, ob jemand Umgehungslösungen für diesen speziellen Fall kennt.
Korrektur: Diese Technik funktioniert nicht mit dem
srcset-Attribut, wie oben geschrieben. Das Problem liegt bei den Leerzeichen in den *Optimized URL-encoded*-Zeichenketten, die Browser zur Trennung der Bild-URL und des Breiten-Deskriptors verwenden. Um dieses Problem zu beheben, müssen Sie Folgendes schreiben.Anstatt
Der Unterschied liegt in den codierten Zeichen. Die des Artikels haben nur
<und>codiert. Diejenige, die sowohl fürsrc- als auch fürsrcset-Attribute funktioniert, hat<,>,"und **Leerzeichen** codiert.Danke!
Hallo, das ist ein sehr interessanter Beitrag.
Ich habe nur eine Frage: Welches Werkzeug verwenden Sie, um SVG in Base64 zu konvertieren?
Vielen Dank
Es gibt viele Werkzeuge online. Sie können sogar Chrome DevTools dafür verwenden.
Es scheint, dass Safari unter iOS 12 kein Observer-Ereignis auslöst (im IntersectionObserver-Polyfill), wenn der SVG-Platzhalter in den Viewport gelangt. Ich untersuche eine Umgehungslösung, aber vorerst ist dies keine produktionsreife Technik.
https://github.com/w3c/IntersectionObserver/tree/master/polyfill