For coders TYPO3 Tech Corner

Webp, Responsive Images und Lazy Loading in TYPO3 einbauen

Webp, Responsive Images und Lazy Loading in TYPO3 einbauen

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 }

Zurück

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