Lazy-Loading: Javascript, IntersectionObserver oder nativ per loading="lazy" Attribut?

  • So, mal wieder was aus dem Bereich Codes. Lazy-Loading von Bildern und Co. Was ist da denn derzeit so angesagt bzw. lässt sich kombinieren? Aktuell verwende ich eigentlich bLazy als fertiges Script. Performant ist das aber nicht wirklich, da es auf Scroll-Eigenschaften im Browserfenster reagiert und die somit ständig auswerten muss, ob ein Bild nun kommt oder nicht, er muss auswerten, wenn sich das Sichtfeld bewegt.


    Daher habe ich nun schon seit 2 Jahren IntersectionObserver am laufen, die eigentlich zu 90% funktionieren. Wenn nicht, dann gibt es einen Fallback zu Lazy-Loading mit Scroll-Überwachung.


    So, nun stößt Chrome und Opera vor und hat das ganze nun seit 3 oder 4 Versionen nativ im Browser, würde also gänzlich ohne Javascript funktionieren. Nur irgendwie scheitere ich da an einer Art Polyfill oder Abgrenzung, was wann wie genutzt wird. Klar, die Erkennung davon, was möglich ist, habe ich, das bringt aber nichts, weil Bilder rein HTML-technisch anders eingebunden werden müssen.


    Für das bisherige Lazy-Loading wird als "src" nur ein Platzhalter gesetzt und die eigentliche URL kommt in "data-src". Ist das Bild dann nahe dem Sichbareich, werden die beiden Angaben getauscht, das Bild geladen.


    Mit dem loading="lazy" Attribut muss die URL aber im "src" bleiben, nur dann kann sie geladen werden, sonst kommt nur der Platzhalter. Und genau hier scheitere ich gerade.


    Hat einer eine Idee?


    Klar könnte ich sagen, wenn loading="lazy" vorhanden ist, also ('loading' in HTMLImageElement.prototype) === true, dann soll das Script, das das Vorhandensein der Funktion testet, alle "data-src" nach "src" kopieren und den Rest dann dem Browser überlassen. So wie ich das aber bisher gesehen habe ist das einfache kopieren von "data-src" nach "src" von der Performance her schlechter, also es so zu lassen wie es ist und den IntersectionObserver machen zu lassen.


    Mir scheint fast, als ob da was auf dem Markt kommt, das absolut nicht abwärtskompatibel ist. Fakt ist, wenn das HTML dazu passt, dann ist das neue loading="lazy" schneller und belastet den Browser weniger. Nur wenn man sein HTML direkt so erstellt, dann hat man keine Chance mehr für einen Fallback.


    Einer eine Idee? Übersehe ich was?

  • Mal als Anmerkung:


    Bei bLazy oder eben anderen individuellen Javascripten für Lazyload wird ein Bild so eingebunden:


    <img class="b-lazy" src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==" data-src="/bilder/webseite/geranium-zweifarbig-k.jpg" title="Geranium zweifarbig" alt="Geranie zweifarbig in weiß-rosa" width="300" height="225">


    Das im "src" ist in dem Fall eines der derzeit kleinsten Bilder (gif), das man inline einbinden kann. Ist bei allen Bildern eingebunden, muss also nur einmal geladen bzw. verarbeitet werden. Wenn das Lazy-Load-Script nun erkennt, dass das Bild in die Nähe des Viewports kommt, dann ersetzt es "src" mit "data-src" und das Bild wird eigentlich erst geladen. Der IntersectionObserver arbeitet genauso, ersetzt also auch, aber er ist auch eine native Browser-Funktion, die nicht ständig das "Scroll-Event" überwacht. Nur mal so als Beispiel: Ein kleiner Roll mit der Maus-Rolle ist in der Regel 60 bis 100 Pixel. Das Scroll-Event würde also 60 bis 100 Mal eine Zustandsänderung "abfeuern", nur für eine Rad-Bewegung. IntersectionObserver achtet nicht darauf, der achtet darauf, was im Browser wirklich im oder am Viewport ist.


    Für das neuen loading="lazy" Attribut schaut das HTML aber so aus:


    <img loading="lazy" class="b-lazy" src="/bilder/webseite/geranium-zweifarbig-k.jpg" title="Geranium zweifarbig" alt="Geranie zweifarbig in weiß-rosa" width="300" height="225">


    Die SRC ist also direkt angegeben. Der Browser, der das kann, der verhindert das Laden des Bildes, bis es in die Nähe des Viewports kommt. Wenn es in der Nähe ist, dann lädt der Browser erst mal mehr oder weniger die Meta-Daten des Bilder und nicht das Bild selbst. Meta-Daten nur, damit Breite und Höhe positioniert und gerendert werden können. Kommt es dann in den Viewport, dann lädt er das Bild. Das hat den Vorteil, dass das "Zucken" von Webseiten weg ist, denn der Browser weiß vorher, wie groß das eigentliche Bild ist. Das vom Platzhalter hat ja meist andere Dimensionen, in der Regel 1x1 und eben nicht 4x3 oder 16x9 oder sonst was.


    Da nun aber das Bild direkt im SRC steht gibt es anscheinend keine Möglichkeit, das mit Lazyload per Javascript zu kombinieren, als Fallback. Oder hat einer eine Idee?

  • Also technisch für den Betreiber einer Webseite macht das Attribut loading="lazy" eigentlich nur Sinn, wenn er vorher nichts getan hat und zugleich seine Seiten jeweils sehr lang sind. Wenn bereits ein IntersectionObserver läuft, dann erzeugt das neue Attribut eigentlich mehr Serverlast, weil mehr Daten geladen werden, direkt. Bei den meisten Seiten, die eine gewisse Höhe nicht überschreiten, ändert sich damit gar nichts. Die sparen keine Datenlast und keine Ladezeit ein.


    Bei mir lädt der IntersectionObserver z.B. Bilder erst, wenn sie 20px unterhalb oder oberhalb vom Viewport sind. Mit loading="lazy" hat man diese Option nicht, man kann es nicht steuern, es kommt nativ aus Chrome oder Opera. Sie Serverlast steigt also. Im Vergleich: Der Aufruf einer Bildergalerie bei mir (ohne Scrollen, 4k Monitor, 24 Zoll): Mit IntersectionObserver: 4 Bilder, mit loading="lazy" 18 Bilder. Es sind nur 4 Bilder direkt sichtbar, die anderen unterhalb. Natürlich steigt damit auch das zu übertragende Datenvolumen, und die Seite wird langsamer.


    Den IntersectionObserver kann man also steuern, das Attribut "loading" nicht, das gibt der Browser-Hersteller vor. Aber auch dort gibt es einen "threshold", der meiner Meinung nach aber viel zu hoch ist. Vorteil dennoch, er variiert ja nach Verbindung. Also Mobil mit langsamen oder schnellem Netz, 2G, 3G, 4G etc. Was auch immer der Browser erkennt, er verwendet einen anderen Wert. Dennoch sind alle irgendwie viel zu hoch.


    Aktuell zum Beispiel, im Chrome 78:


    Unbekannte Verbindung: 5000px

    Offline gespeichert: 8000px

    Langsames 2G: 8000px

    Normales 2G: 6000px

    3G: 4000px

    4G: 3000px


    ^^ Diese Daten stammen direkt aus dem Quellcode von Chromium und sind von Chrome und Opera so übernommen. Sie sind für Bilder. Für iFrames gibt es andere, aber eigentlich auch zu hoch.


    Klar, eine langsame Verbindung muss früher laden, dass das Bild da ist, wenn der User da hin scrollt. Bei IntersectionObserver oder LazyLoad per JS kann es passieren, dass das Bild in den Viewport kommt, aber denn 5 Sek braucht, bis es geladen wird. Das verhindert das Attribut loading="lazy" damit, dass es früher lädt. Es ignoriert aber, ob ein User vielleicht direkt abspringt und gar nicht interagiert mit der Seite. Aber dennoch finde ich das hoch angesetzt. Für einen mit langsamen 2G, also GPRS, bedeutet das, dass der auf eine Seite geht und alles geladen wird, was irgendwie 8000px entfernt vom Screen ist, was bei den meisten Seiten ALLES sein dürfte. Für die Nutzung ist das sicherlich gut, für die Datenübertragung und Ladezeit nicht. ("Für die Nutzung ist das sicherlich gut", weil loading="lazy" priorisiert. Das was am nächsten ist sofort, das andere direkt danach - dennoch ist es Traffik extra)


    Erstaunlich ist dennoch, und daher auch der andere Post, dass es wohl keine Rolle spielt, wann was geladen ist und wie viel es an Datenvolumen braucht. Wichtig ist, es ist da, wenn der User es "sehen könnte". Da sind wird also wieder im Bereich "FID". Und ja, der wird besser, obwohl sich Ladezeit und Datenvolumen verschlechtern. Das Gesamtergebnis der Auswertung ist aber besser. Die Prioritäten scheinen sich also verschoben zu haben.


    Anderes Thema, später. Auslagerung von CPU-Aktivität, die auch eine Rolle in der Auswertung spielt, auf die GPU des Rechners. Auch dafür gibt es was Neuest, vorgestern erst zufällig gesehen, wobei das eine CSS-Anweisung ist.