Skip to content

Instantly share code, notes, and snippets.

@lorenzulrich
Last active November 14, 2018 21:00
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save lorenzulrich/d9a680b7989b884011dd24357588f533 to your computer and use it in GitHub Desktop.
Save lorenzulrich/d9a680b7989b884011dd24357588f533 to your computer and use it in GitHub Desktop.
ResponsiveImageViewHelper and SrcSetViewHelper for Responsive Images using lazysizes/bgset
<div class="lazyload" data-bgset="{v:srcSet(image: media.1, ratio: 1, maxWidth: 1920)}">
Foo
</div>
{namespace v=Visol\Foobar\ViewHelpers}
# All sizes variants to be generated for an image up to the maximumWidth
config.responsiveImage.sizes = 16, 48, 96, 160, 320, 480, 640, 960, 1024, 1440, 1920, 2560, 3840, 5120
<v:responsiveImage image="{article.media.0}" class="img-fluid" ratio="1" maxWidth="800"/>
{namespace v=Visol\Foobar\ViewHelpers}
<?php
namespace Visol\Foobar\ViewHelpers;
/* *
* This script is part of the TYPO3 project - inspiring people to share! *
* *
* TYPO3 is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License version 2 as published by *
* the Free Software Foundation. *
* *
* This script is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
* TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
* Public License for more details. *
* */
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Resizes a given image (if required) and renders the respective img tag
*
* = Examples =
*
* <code title="Default">
* <f:image src="EXT:myext/Resources/Public/typo3_logo.png" alt="alt text" />
* </code>
* <output>
* <img alt="alt text" src="typo3conf/ext/myext/Resources/Public/typo3_logo.png" width="396" height="375" />
* or (in BE mode):
* <img alt="alt text" src="../typo3conf/ext/viewhelpertest/Resources/Public/typo3_logo.png" width="396" height="375" />
* </output>
*
* <code title="Image Object">
* <f:image image="{imageObject}" />
* </code>
* <output>
* <img alt="alt set in image record" src="fileadmin/_processed_/323223424.png" width="396" height="375" />
* </output>
*
* <code title="Inline notation">
* {f:image(src: 'EXT:viewhelpertest/Resources/Public/typo3_logo.png', alt: 'alt text', minWidth: 30, maxWidth: 40)}
* </code>
* <output>
* <img alt="alt text" src="../typo3temp/assets/images/f13d79a526.png" width="40" height="38" />
* (depending on your TYPO3s encryption key)
* </output>
*
* <code title="Other resource type (e.g. PDF)">
* <f:image src="fileadmin/user_upload/example.pdf" alt="foo" />
* </code>
* <output>
* If your graphics processing library is set up correctly then it will output a thumbnail of the first page of your PDF document.
* <img src="fileadmin/_processed_/1/2/csm_example_aabbcc112233.gif" width="200" height="284" alt="foo">
* </output>
*
* <code title="Non-existent image">
* <f:image src="NonExistingImage.png" alt="foo" />
* </code>
* <output>
* Could not get image resource for "NonExistingImage.png".
* </output>
*/
class ResponsiveImageViewHelper extends \TYPO3\CMS\Fluid\ViewHelpers\ImageViewHelper
{
const RATIO_PATTERN = '/(\d):(\d)/';
/**
* @var string
*/
protected $tagName = 'img';
/**
* @var \Visol\Foobar\Service\SrcSetService
*/
protected $srcSetService;
/**
* @param \Visol\Foobar\Service\SrcSetService $srcSetService
*/
public function injectSrcSetService(\Visol\Foobar\Service\SrcSetService $srcSetService)
{
$this->srcSetService = $srcSetService;
}
/**
* Initialize arguments.
*/
public function initializeArguments()
{
$this->registerArgument('additionalAttributes', 'array', 'Additional tag attributes. They will be added directly to the resulting HTML tag.', false);
$this->registerArgument('data', 'array', 'Additional data-* attributes. They will each be added with a "data-" prefix.', false);
$this->registerUniversalTagAttributes();
$this->registerTagAttribute('alt', 'string', 'Specifies an alternate text for an image', false);
$this->registerArgument(
'src',
'string',
'a path to a file, a combined FAL identifier or an uid (int).
If $treatIdAsReference is set, the integer is considered the uid of the sys_file_reference record.
If you already got a FAL object, consider using the $image parameter instead'
);
$this->registerArgument('treatIdAsReference', 'bool', 'given src argument is a sys_file_reference record');
$this->registerArgument('image', 'object', 'a FAL object');
$this->registerArgument('crop', 'string|bool', 'overrule cropping of image (setting to FALSE disables the cropping set in FileReference)');
$this->registerArgument('cropVariant', 'string', 'select a cropping variant, in case multiple croppings have been specified or stored in FileReference', false, 'default');
$this->registerArgument('sizes', 'string', 'Comma-separated list of image sizes');
$this->registerArgument('ratio', 'string', 'Ratio of the image. This can be a float value (e.g. 1.5) or a ratio string (e.g. 1:2)');
$this->registerArgument('maxWidth', 'int', 'maximum width of the image');
$this->registerArgument('maxHeight', 'int', 'minimum width of the image');
$this->registerArgument('absolute', 'bool', 'Force absolute URL', false, false);
}
/**
* Resizes a given image (if required) and renders the respective img tag
*
* @see https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Image/
*
* @throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
* @return string Rendered tag
*/
public function render()
{
if (is_numeric($this->arguments['ratio'])) {
$ratio = $this->arguments['ratio'];
} elseif (preg_match(static::RATIO_PATTERN, $this->arguments['ratio'], $matches)) {
$ratio = $matches[1] / $matches[2];
}
$maximumWidth = $this->arguments['maxWidth'];
$maximumHeight = $this->arguments['maxHeight'];
$crop = $this->arguments['crop'];
$cropVariant = $this->arguments['cropVariant'];
if ((is_null($this->arguments['src']) && is_null($this->arguments['image'])) || (!is_null($this->arguments['src']) && !is_null($this->arguments['image']))) {
throw new \TYPO3\CMS\Fluid\Core\ViewHelper\Exception('You must either specify a string src or a File object.', 1382284106);
}
try {
$image = $this->imageService->getImage($this->arguments['src'], $this->arguments['image'], $this->arguments['treatIdAsReference']);
if ($this->arguments['sizes']) {
$sizesCsv = $this->arguments['sizes'];
} else {
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
$configurationManager = $objectManager->get(\TYPO3\CMS\Extbase\Configuration\ConfigurationManager::class);
$extbaseFrameworkConfiguration = $configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
$sizesCsv = $extbaseFrameworkConfiguration['config.']['responsiveImage.']['sizes'];
}
$sizes = GeneralUtility::intExplode(',', $sizesCsv, true);
$srcSetString = $this->srcSetService->getSrcSetAttribute($image, $ratio, $maximumWidth, $maximumHeight, $crop, $cropVariant, $sizes, null);
$classNames = ['lazyload'];
if (isset($this->arguments['class'])) {
$classNames[] = $this->arguments['class'];
}
$this->tag->addAttributes(
[
'class' => implode(' ', $classNames),
'data-sizes' => 'auto',
'data-srcset' => $srcSetString,
'srcset' => 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=='
]
);
$alt = $image->getProperty('alternative');
$title = $image->getProperty('title');
// The alt-attribute is mandatory to have valid html-code, therefore add it even if it is empty
if (empty($this->arguments['alt'])) {
$this->tag->addAttribute('alt', $alt);
}
if (empty($this->arguments['title']) && $title) {
$this->tag->addAttribute('title', $title);
}
} catch (ResourceDoesNotExistException $e) {
// thrown if file does not exist
} catch (\UnexpectedValueException $e) {
// thrown if a file has been replaced with a folder
} catch (\RuntimeException $e) {
// RuntimeException thrown if a file is outside of a storage
} catch (\InvalidArgumentException $e) {
// thrown if file storage does not exist
}
return $this->tag->render();
}
}
<?php
namespace Visol\Foobar\Service;
/*
* This file is inspired by the Visol.Neos.ResponsiveImages package.
*
* (c) visol digitale Dienstleistungen GmbH, www.visol.ch
*
* This package is Open Source Software. For the full copyright and license
* information, please view the LICENSE file which was distributed with this
* source code.
*/
use TYPO3\CMS\Core\Imaging\ImageManipulation\CropVariantCollection;
use TYPO3\CMS\Core\Resource\AbstractFile;
use TYPO3\CMS\Core\Resource\FileInterface;
/**
* Render the srcset attribute with responsive images. Accepts mostly the same parameters as the uri.image ViewHelper of the Neos.Media package:
* asset, maximumWidth, maximumHeight, allowCropping, ratio.
*
*/
class SrcSetService
{
/**
* @var \TYPO3\CMS\Extbase\Service\ImageService
*/
protected $imageService;
/**
* @param \TYPO3\CMS\Extbase\Service\ImageService $imageService
*/
public function injectImageService(\TYPO3\CMS\Extbase\Service\ImageService $imageService)
{
$this->imageService = $imageService;
}
/**
* Returns a processed image path
*
* @param FileInterface $image
* @param float $ratio
* @param int $maximumWidth
* @param int $maximumHeight
* @param boolean $allowCropping
* @param int $quality
* @param array $sizes
* @param RequestInterface $request
* @return string
* @throws \Exception
*/
public function getSrcSetAttribute($image, $ratio, $maximumWidth, $maximumHeight, $crop, $cropVariant, array $sizes, $absolute = false)
{
if (!is_array($sizes) || !count($sizes) > 0) {
throw new \Exception('No sizes defined.', 1519837126);
}
if (!$image instanceof FileInterface) {
throw new \Exception('No asset given for rendering.', 1519844659);
}
if ($image->getProperty('type') == AbstractFile::FILETYPE_IMAGE) {
$assetWidth = $image->getProperty('width');
$assetHeight = $image->getProperty('height');
}
$cropString = $crop;
if ($cropString === null && $image->hasProperty('crop') && $image->getProperty('crop')) {
$cropString = $image->getProperty('crop');
}
$cropVariantCollection = CropVariantCollection::create((string)$cropString);
$cropVariant = $cropVariant ?: 'default';
$cropArea = $cropVariantCollection->getCropArea($cropVariant);
$srcSetData = [];
foreach ($sizes as $size) {
$currentWidth = null;
$currentMaximumWidth = $size;
$currentHeight = null;
$currentMaximumHeight = null;
if ($currentMaximumWidth > $assetWidth) {
continue;
}
if (isset($maximumWidth) && $currentMaximumWidth > $maximumWidth) {
continue;
}
if ($ratio) {
$currentWidth = $currentMaximumWidth;
$currentMaximumHeight = $size / $ratio;
$currentHeight = $currentMaximumHeight;
if ($currentMaximumHeight > $assetHeight) {
continue;
}
if (isset($maximumHeight) && $currentMaximumHeight > $maximumHeight) {
continue;
}
}
$processingInstructions = [
'width' => $currentWidth . 'c',
'height' => $currentHeight . 'c',
'maxWidth' => $currentMaximumWidth,
'maxHeight' => $currentMaximumHeight,
'crop' => $cropArea->isEmpty() ? null : $cropArea->makeAbsoluteBasedOnFile($image),
];
$processedImage = $this->imageService->applyProcessingInstructions($image, $processingInstructions);
$imageUri = $this->imageService->getImageUri($processedImage, $absolute);
if ($imageUri === null) {
continue;
}
$srcSetData[] = $imageUri . ' ' . $processedImage->getProperty('width') . 'w ' . $processedImage->getProperty('height') . 'h ';
}
return implode(', ', $srcSetData);
}
}
<?php
namespace Visol\Foobar\ViewHelpers;
/* *
* This script is part of the TYPO3 project - inspiring people to share! *
* *
* TYPO3 is free software; you can redistribute it and/or modify it under *
* the terms of the GNU General Public License version 2 as published by *
* the Free Software Foundation. *
* *
* This script is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
* TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
* Public License for more details. *
* */
use TYPO3\CMS\Core\Resource\Exception\ResourceDoesNotExistException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
/**
* Resizes a given image (if required) and renders the respective img tag
*
* = Examples =
*
* <code title="Default">
* <f:image src="EXT:myext/Resources/Public/typo3_logo.png" alt="alt text" />
* </code>
* <output>
* <img alt="alt text" src="typo3conf/ext/myext/Resources/Public/typo3_logo.png" width="396" height="375" />
* or (in BE mode):
* <img alt="alt text" src="../typo3conf/ext/viewhelpertest/Resources/Public/typo3_logo.png" width="396" height="375" />
* </output>
*
* <code title="Image Object">
* <f:image image="{imageObject}" />
* </code>
* <output>
* <img alt="alt set in image record" src="fileadmin/_processed_/323223424.png" width="396" height="375" />
* </output>
*
* <code title="Inline notation">
* {f:image(src: 'EXT:viewhelpertest/Resources/Public/typo3_logo.png', alt: 'alt text', minWidth: 30, maxWidth: 40)}
* </code>
* <output>
* <img alt="alt text" src="../typo3temp/assets/images/f13d79a526.png" width="40" height="38" />
* (depending on your TYPO3s encryption key)
* </output>
*
* <code title="Other resource type (e.g. PDF)">
* <f:image src="fileadmin/user_upload/example.pdf" alt="foo" />
* </code>
* <output>
* If your graphics processing library is set up correctly then it will output a thumbnail of the first page of your PDF document.
* <img src="fileadmin/_processed_/1/2/csm_example_aabbcc112233.gif" width="200" height="284" alt="foo">
* </output>
*
* <code title="Non-existent image">
* <f:image src="NonExistingImage.png" alt="foo" />
* </code>
* <output>
* Could not get image resource for "NonExistingImage.png".
* </output>
*/
class SrcSetViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper
{
const RATIO_PATTERN = '/(\d):(\d)/';
/**
* @var \TYPO3\CMS\Extbase\Service\ImageService
*/
protected $imageService;
/**
* @var \Visol\Foobar\Service\SrcSetService
*/
protected $srcSetService;
/**
* @param \TYPO3\CMS\Extbase\Service\ImageService $imageService
*/
public function injectImageService(\TYPO3\CMS\Extbase\Service\ImageService $imageService)
{
$this->imageService = $imageService;
}
/**
* @param \Visol\Foobar\Service\SrcSetService $srcSetService
*/
public function injectSrcSetService(\Visol\Foobar\Service\SrcSetService $srcSetService)
{
$this->srcSetService = $srcSetService;
}
/**
* Initialize arguments.
*/
public function initializeArguments()
{
$this->registerArgument(
'src',
'string',
'a path to a file, a combined FAL identifier or an uid (int).
If $treatIdAsReference is set, the integer is considered the uid of the sys_file_reference record.
If you already got a FAL object, consider using the $image parameter instead'
);
$this->registerArgument('treatIdAsReference', 'bool', 'given src argument is a sys_file_reference record');
$this->registerArgument('image', 'object', 'a FAL object');
$this->registerArgument('crop', 'string|bool', 'overrule cropping of image (setting to FALSE disables the cropping set in FileReference)');
$this->registerArgument('cropVariant', 'string', 'select a cropping variant, in case multiple croppings have been specified or stored in FileReference', false, 'default');
$this->registerArgument('sizes', 'string', 'Comma-separated list of image sizes');
$this->registerArgument('ratio', 'string', 'Ratio of the image. This can be a float value (e.g. 1.5) or a ratio string (e.g. 1:2)');
$this->registerArgument('maxWidth', 'int', 'minimum width of the image');
$this->registerArgument('maxHeight', 'int', 'minimum width of the image');
$this->registerArgument('absolute', 'bool', 'Force absolute URL', false, false);
}
/**
* Resizes a given image (if required) and renders the respective img tag
*
* @see https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Image/
*
* @throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
* @return string Rendered tag
*/
public function render()
{
if (is_numeric($this->arguments['ratio'])) {
$ratio = $this->arguments['ratio'];
} elseif (preg_match(static::RATIO_PATTERN, $this->arguments['ratio'], $matches)) {
$ratio = $matches[1] / $matches[2];
}
$maximumWidth = $this->arguments['maxWidth'];
$maximumHeight = $this->arguments['maxHeight'];
$crop = $this->arguments['crop'];
$cropVariant = $this->arguments['cropVariant'];
if ((is_null($this->arguments['src']) && is_null($this->arguments['image'])) || (!is_null($this->arguments['src']) && !is_null($this->arguments['image']))) {
throw new \TYPO3\CMS\Fluid\Core\ViewHelper\Exception('You must either specify a string src or a File object.', 1382284106);
}
try {
$image = $this->imageService->getImage($this->arguments['src'], $this->arguments['image'], $this->arguments['treatIdAsReference']);
if ($this->arguments['sizes']) {
$sizesCsv = $this->arguments['sizes'];
} else {
$objectManager = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance(\TYPO3\CMS\Extbase\Object\ObjectManager::class);
$configurationManager = $objectManager->get(\TYPO3\CMS\Extbase\Configuration\ConfigurationManager::class);
$extbaseFrameworkConfiguration = $configurationManager->getConfiguration(\TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface::CONFIGURATION_TYPE_FULL_TYPOSCRIPT);
$sizesCsv = $extbaseFrameworkConfiguration['config.']['responsiveImage.']['sizes'];
}
$sizes = GeneralUtility::intExplode(',', $sizesCsv, true);
$srcSetString = $this->srcSetService->getSrcSetAttribute($image, $ratio, $maximumWidth, $maximumHeight, $crop, $cropVariant, $sizes, null);
} catch (ResourceDoesNotExistException $e) {
// thrown if file does not exist
} catch (\UnexpectedValueException $e) {
// thrown if a file has been replaced with a folder
} catch (\RuntimeException $e) {
// RuntimeException thrown if a file is outside of a storage
} catch (\InvalidArgumentException $e) {
// thrown if file storage does not exist
}
return $srcSetString;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment