Ein optimiertes Frontend für noch mehr Performance: Lazy Loading in TYPO3 10 LTS – Teil 2
Die Hybrid-Lösung – das Beste aus beiden Welten!
Dankenswerterweise kann man abfragen, ob ein Browser das loading-Attribut unterstützt. So ist es möglich nur dann zusätzliches Javascript zu laden, wenn es notwendig ist. Darüber hinaus ist es kein Problem, wenn das loading-Attribut gesetzt ist, obwohl es der Browser nicht unterstützt. In diesem Fall wird es ignoriert. Noch besser: In dem Blog-Artikel über die Einführung zur Unterstützung des loading-Attributs hat Google direkt erklärt, wie man das konkret umsetzt.
Für unser Beispiel müssen wir nur wenige Anpassungen machen, da wir lazysizes weiter verwenden werden. Wir nehmen als Basis unser Snippet für lazysizes und fügen neben dem loading-Attribut noch die Weiche hinzu:
<html>
<head>
<title>Beispielseite</title>
</head>
<body>
<img loading="lazy" class="lazyload" data-src="img1.png" style="margin-top: 10000px;"/>
<script>
if ('loading' in HTMLImageElement.prototype) {
const images = document.querySelectorAll('img[loading="lazy"]');
images.forEach(img => {
img.src = img.dataset.src;
});
} else {
const script = document.createElement('script');
script.src =
'lazysizes.min.js';
document.body.appendChild(script);
}
</script>
</body>
</html>
Die Weiche sorgt dafür, dass die data-src-Angaben direkt in src umgeschrieben werden, sofern das loading-Attribut unterstützt wird. Ist das nicht der Fall, wird die lazysizes.min.js geladen und übernimmt wieder das Lazy Loading.
Etwas Kompatibilität reinbringen…
Diese Lösung wird aber leider nicht vom Internet Explorer 11 unterstützt :/
Wir sind aber fast am Ziel! Für die IE 11 Unterstützung müssen wir nur auf die arrow-function verzichten:
<html>
<head>
<title>Beispielseite</title>
</head>
<body>
<img loading="lazy" class="lazyload" data-src="img1.png" style="margin-top: 10000px;"/>
<script>
if ('loading' in HTMLImageElement.prototype) {
var images = document.querySelectorAll('img[loading="lazy"]');
images.forEach(function (img) {
img.src = img.dataset.src;
});
} else {
const script = document.createElement('script');
script.src =
'lazysizes.min.js';
document.body.appendChild(script);
}
</script>
</body>
</html>
Verwenden wir einen Browser, der das loading-Attribut unterstützt, können wir sehen, dass lazysizes nicht geladen wird. Stattdessen greift das Lazy Loading über das loading-Attribut:
Wolltest du nicht über TYPO3 reden?
Ach ja richtig, eigentlich wollte ich über TYPO3 reden! Als ich letztes Jahr den Blog-Artikel auf web.dev gelesen habe, war mein erster Gedanke: „Nice, das schaust du dir genauer an!“. Da die Unterstützung des loading-Attributs damals aber nur in Chrome gegeben war, habe ich es immer wieder vor mir hergeschoben. Dann kam alles zusammen: Edge hat die Unterstützung im Januar mit Version 79 gebracht, Firefox ist im April nachgezogen und TYPO3 10.3 brachte es als Default-Einstellung in fluid_styled_content.
Wenn ihr also fluid_styled_content nutzt, profitieren alle Websitebesucher (mit kompatiblen Browsern) vom Lazy Loading. Dafür müsst ihr auch kein zusätzliches Javascript eingebunden haben.
Darüber hinaus sehe ich es als wichtig an, solche Techniken schnellstmöglich einer breiten Masse nahe zu bringen. Welch Freude wäre es doch, wenn man eines Tages gar kein Javascript mehr brauchen würde, um Elemente erst dann vom Browser laden zu lassen, wenn sie gebraucht werden.
Unabhängig davon, dass das loading-Attribut nun standardmäßig in fluid_styled_content genutzt wird, ist es sinnvoll, über eine eigene Implementierung nachzudenken. Sei es, weil man nicht auf fluid_styled_content setzt oder weil man das hybride Verfahren nutzen möchte. Ich kann zwar nicht in eure Sitepackages schauen, um für jeden eine individuelle Lösung zu bauen, aber vielleicht hilft ein erster Denkanstoß für eine eigene Implementierung.
Eigene Implementierung
Für das folgende Beispiel habe ich ein minimalistisches Sitepackage über sitepackagebuilder.com auf Basis von fluid_styled_content erstellt. Anschließend habe ich die Partials von fluid_styled_content in das Sitepackage kopiert, um sie bearbeiten zu können. Dabei fokussiere ich mich weiterhin nur auf Bilder. Schauen wir uns erst einmal an, was in dem zugehörigen Partial Media/Rendering/Image.html passiert:
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data
-namespace-typo3-fluid="true">
<f:media class="image-embed-item" file="{file}" width="{dimensions.width}"
height="{dimensions.height}" alt="{file.alternative}" title="{file.title}"
loading="{settings.media.lazyLoading}" />
</html>
Mithilfe des media-Viewhelpers erstellt fluid_styled_content ein img-Tag, bei dem die wichtigsten Angaben (Bildquelle, Größe, Titel etc.) und das loading-Attribut aus settings.media.lazyloading – wie im Changelog beschrieben – befüllt werden. Das „Problem“ für die hybride Lösung: Es wird immer ein src-Attribut mit dem (generierten) Bild erzeugt. Für unsere hybride Lösung benötigen wir allerdings nur ein data-src-Attribut, welches wir per Javascript umschreiben.
Um das zu erreichen, können wir beispielsweise auf f:uri.image zurückgreifen. Damit können wir das data-src-Attribut direkt befüllen. Die weiteren Einstellungen zum Title etc. behalten wir bei und ergänzen darüber hinaus noch die CSS-Klasse „lazyload“, die wir für lazysizes benötigen:
<img data-src="{f:uri.image(src:'{file.uid}', treatIdAsReference:'1',
width:'{dimensions.width}, height:{dimensions.height}')}"
alt="{file.alternative}" title="{file.title}"
loading="{settings.media.lazyLoading}" class="image-embed-item lazyload"
style="margin-top: 2000px;">
Unsere img-Tags werden damit schon passend generiert. Dass ich hier jedes Bild wiederum mit einem sehr großen margin-top von 2000px versehe, soll nur dazu dienen, die Funktion testen zu können. In einem echten Template solltet ihr das natürlich nicht machen. ;-)
Als nächstes müssen wir die Einbindung des Javascripts vornehmen. Dafür benötigen wir lazysizes selbst, was ich deshalb in dem Sitepackage unter typo3conf/ext/testpackage/Resources/Public/JavaScript/Dist/azysizes.min.js in der minifizierten Version abgelegt habe. Darüber hinaus habe ich ein zusätzliches Javascript erstellt, welches die Weiche enthält und in meinem Fall an der gleichen Stelle als hybrid.js liegt:
if ('loading' in HTMLImageElement.prototype) {
var images = document.querySelectorAll('img[loading="lazy"]');
images.forEach(function (img) {
img.src = img.dataset.src;
});
} else {
const script = document.createElement('script');
script.src =
'/typo3conf/ext/testpackage/Resources/Public/JavaScript/Dist/lazysizes.min.js'
;
document.body.appendChild(script);
}
Damit dieses Javascript geladen wird, fügen wir es dem Footer per TypoScript hinzu:
page.includeJSFooter.hybridlazyloading =
EXT:testpackage/Resources/Public/JavaScript/Dist/hybrid.js
An dieser Stelle habe ich mich gegen die Einbindung als inline-Javascript entschieden, damit TYPO3 unser hybrid.js bspw. per config.concatenateJs = 1 auch mit anderen Skripten zusammenführen kann. Für die Erfolgskontrolle legen wir uns einfachheitshalber eine Seite an, auf der es nur ein paar Bildelemente gibt. In Aktion siehts dann so aus:
Übrigens ist das loading-Attribut auch in picture-Tags nutzbar (genauso wie lazysizes auch ;-)). Schauen wir uns das Beispiel von lazysizes auf https://afarkas.github.io/lazysizes/index.html an, müssen wir nur im img-Tag das loading-Attribut ergänzen:
<picture>
<!--[if IE 9]><video style="display: none"><![endif]-->
<source
data-srcset="500.jpg"
media="(max-width: 500px)" />
<source
data-srcset="1024.jpg"
media="(max-width: 1024px)" />
<source
data-srcset="1200.jpg" />
<!--[if IE 9]></video><![endif]-->
<img
src="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAI
CTAEAOw=="
data-src="1024.jpg"
class="lazyload"
alt="image with artdirection"
loading="lazy"/>
</picture>
Obwohl der img-Tag eigentlich nur als Fallback dient, genügt es hier das loading-Attribut zu nutzen, damit es vom Browser auch für die data-srcset-Elemente berücksichtigt wird. (Hier in diesem Beispiel ist ein Platzhalter im src-Attribut gesetzt, der allerdings nicht zwingend notwendig ist.) Eine Implementierung in bestehende Sitepackages mit picture-Tags sollte daher ebenfalls einfach möglich sein. :-)
Und nun: Viel Spaß bei der Umsetzung!
Wie schaut es bei euch mit der Verwendung des loading-Attributs aus? Nutzt ihr es schon für Projekte? Findet ihr die Javascript-Lösungen besser oder setzt ihr auf Hybrid-Lösungen?