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 }

TYPO3: Finding pages in mixed mode

In TYPO3, Mixed Mode refers to translated pages that contain content only partially related to the corresponding content in the main language. This is indicated in the backend by an error message. But...

Zum Beitrag

Extbase Extensions: Think extensibility with data, site and language

Today, I have a small request for the TYPO3 extension authors out there: Make sure your extensions are extensible. This will also promote the distribution of the corresponding plugins.

Zum Beitrag

SQL: Show all tables sorted by size in descending order

Lately I've been using the SQL command more often to find out which tables in the TYPO3 database are the largest. I've published the snippet once.

Zum Beitrag

TYPO3 12 with CKEditor 5: Styles in a single selection

If you set a link in the RTE in TYPO3, you may have to choose between different link classes, for example to create buttons in the frontend. What's new in TYPO3 12 is that you can select not just one...

Zum Beitrag

Null-Safe Operator in the TYPO3 area

With the introduction of PHP8, problems with undefined arrays or variables in general can arise in many places. Here are a few examples and simple solutions.

Zum Beitrag

Delete the first/last lines of a (SQL) file

There isn't much to say about the following commands. Sometimes it can be useful to delete the first (or last) X lines from a file. And if the file is too large to open with a conventional program, a...

Zum Beitrag