For coders TYPO3 Tech Corner

Webp, Responsive Images und Lazy Loading in TYPO3 einbauen

Webp, Responsive Images und Lazy Loading in TYPO3 einbauen

Du willst Google & Co. oder die SEO-Agentur glücklich machen? Dann haben wir hier ein kleines Snippet, mit dem du Bilder im modernen webp-Format, optimiert für das Besucher Endgerät und mit Lazy Loading ausliefern kannst.

Mit nachfolgendem Snippet, kannst du Bilder die über CType textmedia, textpic und image gerendert werden ganz einfach optimieren:

  • WEBP: Bilder sowohl in einem klassischen Format (z.B. PNG für ältere Browser) als auch im neuen WEBP Format mit meist besserer Kompression und damit kleiner ausliefern. Hinweis: Geht ohne Änderung erst ab TYPO3 10
  • Responsive Images: Bilder sollen in der Größe geladen werden, die passend für das mobile Endgerät sind. So brauche ich keine Bilder mit 2000px Breite an Smartphones auszuliefern.
  • Lazy Loading: Assets sollen erst dann geladen werden, wenn der Besucher zu dem Bild hinscrollt. Damit muss beim initialen Laden der Seite weniger Daten übertragen werden.

1. Einfaches Beispiel

Wenn ihr sowieso schon fluid_styled_content im Einsatz habt, könnt ihr das Partial "Media/Rendering/Image.html" einfach in eurem Sitepackage überschreiben:

<html xmlns:f="https://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> <picture> <source srcset="{f:uri.image(image:file, maxWidth: 2561, fileExtension: 'webp')}" media="(min-width: 1601px)" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 2560, fileExtension: 'jpg')}" media="(min-width: 1601px)" type="image/jpeg"> <source srcset="{f:uri.image(image:file, maxWidth: 1601, fileExtension: 'webp')}" media="(min-width: 1201px)" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 1600, fileExtension: 'jpg')}" media="(min-width: 1201px)" type="image/jpeg"> <source srcset="{f:uri.image(image:file, maxWidth: 1201, fileExtension: 'webp')}" media="(min-width: 769px)" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 1200, fileExtension: 'jpg')}" media="(min-width: 769px)" type="image/jpeg"> <source srcset="{f:uri.image(image:file, maxWidth: 769, fileExtension: 'webp')}" media="(min-width: 481px)" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 768, fileExtension: 'jpg')}" media="(min-width: 481px)" type="image/jpeg"> <source srcset="{f:uri.image(image:file, maxWidth: 481, fileExtension: 'webp')}" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 480, fileExtension: 'jpg')}" type="image/jpeg"> <img class="image-embed-item" title="{file.title}" alt="{file.description -> f:format.nl2br()}" src="{f:uri.image(image:file, maxWidth: 1600)}" width="{dimensions.width}" height="{dimensions.height}" loading="lazy" /> </picture> </html>

Hierbei rendert der Browser Zeile für Zeile und übernimmt die erste für ihn gültige Einstellung (z.B. Safari auf Desktop Bild als JPG mit 2560px maximaler Breite, alter IE nutzt den klassischen img-tag, moderne Browser dann webp je nach verfügbarer Bildschirmbreite).

Vergesst nicht, webp generell als Bild-Format in eurer LocalConfiguration zulassen:

$GLOBALS['TYPO3_CONF_VARS']['GFX']['imagefile_ext'] = 'gif,jpg,jpeg,tif,tiff,bmp,pcx,tga,png,pdf,ai,svg,webp';

2. Ausführliches Beispiel mit mehr Logik

Das alleine hilft euch bereits, aber ihr könnt es auch noch auf die Spitze treiben. Davon ausgehend, dass Bilder die neben einem Text in einer Spalte angezeigt werden in der Regel viel weniger Breite haben als ein Bild ober- oder unterhalb eines Textes könnt ihr auch noch mit einem DataProcessor nachhelfen.

<?php declare(strict_types=1); namespace In2code\In2template\DataProcessing; use TYPO3\CMS\Extbase\Object\Exception; use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer; use TYPO3\CMS\Frontend\DataProcessing\GalleryProcessor as GalleryProcessorCore; /** * Class GalleryProcessor * extends the original GalleryProcessor with additional information of the maximum image width * for a better responsive image rendering in Image partial. */ class GalleryProcessor extends GalleryProcessorCore { /** * @var int[] */ protected $maxWidths = [ 'full' => 1600, // maximum image width if image is over or under a text 'beside' => 800 // maximum image width if image is placed beside a text ]; /** * Max width for fullwidthgallery. Related to the number of images that are included in backend, the maximum width * is calculated. For whatever reason if you only add 3 images, they are very small, but if you add 4 images (2x2) a * single image can be very large, etc... * * @var array */ protected $maxWidthsFullWidthGallery = [ 1 => 450, // number of images => max width 2 => 450, 3 => 450, 4 => 1400, // Change from 1 to 2 rows 5 => 450, 6 => 900, 7 => 450, 8 => 650, 9 => 450, 10 => 520, 11 => 450, 12 => 450, 13 => 450, // Change from 2 to 3 rows 14 => 450 ]; /** * @param ContentObjectRenderer $cObj The data of the content element or page * @param array $contentObjectConfiguration The configuration of Content Object * @param array $processorConfiguration The configuration of this processor * @param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View) * @return array the processed data as key/value store * @throws Exception */ public function process( ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData ) { $processedData = parent::process($cObj, $contentObjectConfiguration, $processorConfiguration, $processedData); $processedData = $this->extendDimensions($processedData); return $processedData; } /** * @param array $processedData * @return array */ protected function extendDimensions(array $processedData): array { foreach ((array)$processedData['gallery']['rows'] as $rowKey => $row) { foreach ((array)$row['columns'] as $columnKey => $column) { if (!empty($column['dimensions'])) { if ((int)$processedData['data']['layout'] !== 30) { $maxWidth = $this->calculateMaxWidthForTextMedia($processedData, $row); } else { $maxWidth = $this->calculateMaxWidthForFullWidthGallery($processedData['gallery']['rows']); } $processedData['gallery']['rows'][$rowKey]['columns'][$columnKey]['dimensions']['maxWidth'] = $maxWidth; } } } return $processedData; } /** * @param array $processedData * @param array $row * @return int */ protected function calculateMaxWidthForTextMedia(array $processedData, array $row): int { $maxWidth = $this->maxWidths['full']; if ($processedData['data']['imageorient'] !== 0 && $processedData['data']['imageorient'] !== 8) { $maxWidth = $this->maxWidths['beside']; } if (count($row) > 0) { $maxWidth = (int)ceil($maxWidth / count($row)); } return $maxWidth; } /** * @param array $rows * @return int */ protected function calculateMaxWidthForFullWidthGallery(array $rows): int { $numberOfImages = count($rows); $maxWidth = $this->maxWidthsFullWidthGallery[0]; if (array_key_exists($numberOfImages, $this->maxWidthsFullWidthGallery)) { $maxWidth = $this->maxWidthsFullWidthGallery[$numberOfImages]; } return $maxWidth; } }

Auf die neuen Informationen könnt ihr dann im Partial mit if-conditions eingehen:

<html xmlns:f="https://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true"> <picture> <f:comment> {dimensions.maxWidth} is calculated with a DataProcessor and should show the maximum possible image width. This depends if an image is shown above a text or beside a text (half as width) and in addition how many image columns are available </f:comment> <f:if condition="{dimensions.maxWidth} > 1601"> <source srcset="{f:uri.image(image:file, maxWidth: 2561, fileExtension: 'webp')}" media="(min-width: 1601px)" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 2560, fileExtension: 'jpg')}" media="(min-width: 1601px)" type="image/jpeg"> </f:if> <f:if condition="{dimensions.maxWidth} > 1201"> <source srcset="{f:uri.image(image:file, maxWidth: 1601, fileExtension: 'webp')}" media="(min-width: 1201px)" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 1600, fileExtension: 'jpg')}" media="(min-width: 1201px)" type="image/jpeg"> </f:if> <f:if condition="{dimensions.maxWidth} > 769"> <source srcset="{f:uri.image(image:file, maxWidth: 1201, fileExtension: 'webp')}" media="(min-width: 769px)" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 1200, fileExtension: 'jpg')}" media="(min-width: 769px)" type="image/jpeg"> </f:if> <f:if condition="{dimensions.maxWidth} > 481"> <source srcset="{f:uri.image(image:file, maxWidth: 769, fileExtension: 'webp')}" media="(min-width: 481px)" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 768, fileExtension: 'jpg')}" media="(min-width: 481px)" type="image/jpeg"> </f:if> <source srcset="{f:uri.image(image:file, maxWidth: 481, fileExtension: 'webp')}" type="image/webp"> <source srcset="{f:uri.image(image:file, maxWidth: 480, fileExtension: 'jpg')}" type="image/jpeg"> <img class="image-embed-item" title="{file.title}" alt="{file.description -> f:format.nl2br()}" src="{f:uri.image(image:file, maxWidth: 1600)}" width="{dimensions.width}" height="{dimensions.height}" loading="{settings.media.lazyLoading}" /> </picture> </html>

So lässt sich dann der Prozessor über TypoScript einbinden:

tt_content.textmedia.dataProcessing { // Overwrite original GalleryProcessor 20 = In2code\In2template\DataProcessing\GalleryProcessor }

Kennst du das: Immer nur schnell schnell?

Wie wäre es einmal mit Zeit und Respekt für Codequalität? Arbeiten im Team? Automatisierte Tests?

Komm zu uns

Unterrichtsausfall - und nun? Neues Portal für das Bayerische Staatsministerium für Unterricht und Kultus

Für das Bayerische Staatsministerium für Unterricht und Kultus durften wir eine neue WebApp bzw. ein neues Portal auf Basis eines bereits bestehenden, aber stark veralteten Portals entwickeln. Das...

Zum Beitrag

SQL: Zeige alle Tabellen absteigend nach Größe sortiert

Ich brauche in letzter Zeit häufiger den SQL-Befehl, um herauszufinden, welche Tabellen in der TYPO3-Datenbank am größten sind. Ich habe das Snippet einmal veröffentlicht.

Zum Beitrag

TYPO3 12 mit CKEditor 5: Stile als Einfachauswahl

Wenn man im RTE in TYPO3 einen Link setzt, kann es sein, dass man zwischen verschiedenen Link-Klassen auswählen muss, um beispielsweise Buttons im Frontend zu erzeugen. Neu ist in TYPO3 12 dass man...

Zum Beitrag

Null-Safe Operator im TYPO3-Bereich

Spätestens mit dem Einzug von PHP8 kann es an vielen Stellen zu Problemen mit undefinierten Arrays oder Variablen im Allgemeinen kommen. Hier ein paar Beispiele und einfache Lösung dafür.

Zum Beitrag

Die ersten/letzten Zeilen einer (SQL)-Datei löschen

Zu den nachfolgenden Befehlen gibt es eigentlich nicht viel zu sagen. Manchmal kann es nützlich sein, die ersten (bzw. letzten) X Zeilen aus einer Datei zu löschen. Und wenn die Datei zu groß zum...

Zum Beitrag

b13/container: Kindelemente in der Bearbeitungsansicht hinzufügen und ändern

Anders als in gridelements kann man in der Extension Container von B13 die Kindelemente nicht verwalten, wenn man den Container in der Bearbeitungsansicht öffnet. Wie man das schnell selber einbauen...

Zum Beitrag