Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
A twig extension + template for ProcessWire projects that generates responsive images!
// make sure to register the extension on your twig environment
$twig_env->addExtension(new \MoritzLost\ResponsiveImages());
{#
# Renders a responsive image tag.
#
# @var ProcessWire\Pageimage image The image to display.
# @var array classes Optional classes for the image.
# @var int width The standard / fallback width for the image.
# @var int height The standard height for the image.
# @var array sizes The sizes queries for the responsive image.
# @var array variant_factors The variant factors for the responsive image.
#}
{% set classes = classes|default([]) %}
{% set sizes = sizes is not empty ? sizes|join(', ') : '100vw' %}
{% set variant_factors = variant_factors is not empty
? variant_factors
: [0.25, 0.5, 0.75, 1, 1.5, 2]
-%}
{%- set srcset -%}
{%- if width and height -%}
{{- get_srcset(image, width, height, variant_factors) -}}
{%- elseif width -%}
{{- get_srcset_by_width(image, width, variant_factors) -}}
{%- elseif height -%}
{{- get_srcset_by_height(image, height, variant_factors) -}}
{%- else -%}
{{- get_srcset(image, null, null, variant_factors) -}}
{%- endif -%}
{%- endset -%}
<img srcset="{{ srcset }}" sizes="{{ sizes }}" src="{{ image.url }}"
{%- if image.description %} alt="{{ image.description }}"{% endif -%}
{%- if classes is not empty %} class="{{ classes|join(' ') }}"{% endif %}>
<?php
namespace MoritzLost;
use Processwire\Pageimage;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
class ResponsiveImages extends AbstractExtension
{
public function getFunctions()
{
return [
new TwigFunction('get_srcset', [$this, 'getSrcset']),
new TwigFunction('get_srcset_by_width', [$this, 'getSrcsetByWidth']),
new TwigFunction('get_srcset_by_height', [$this, 'getSrcsetByHeight']),
];
}
/**
* Returns a srcset string containing a list of sources for the passed image,
* based on the provided width, height, and variant factors. All required images
* will be created automatically.
*
* @param Pageimage $img The base image. Must be passed in the largest size available.
* @param int|null $standard_width The standard width for the generated image. Use NULL to use the inherent width of the passed image.
* @param int|null $standard_height The standard height for the generated image. Use NULL to use the inherent height of the passed image.
* @param array|null $variant_factors The multiplication factors for the alternate resolutions.
* @return string
*/
public function getSrcset(
Pageimage $img,
?int $standard_width = null,
?int $standard_height = null,
array $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]
): string {
// use inherit dimensions of the passed image if standard width/height is empty
if (empty($standard_width)) {
$standard_width = $img->width();
}
if (empty($standard_height)) {
$standard_height = $img->height();
}
$suffix = 'auto_srcset';
// get original image for resizing
$original_image = $img->getOriginal() ?? $img;
// the default image for the src attribute
$default_image = $original_image->size(
$standard_width,
$standard_height,
['upscaling' => false, 'suffix' => $suffix]
);
// build the srcset attribute string, and generate the corresponding widths
$srcset = [];
foreach ($variant_factors as $factor) {
// round up, srcset doesn't allow fractions
$width = ceil($standard_width * $factor);
$height = ceil($standard_height * $factor);
// we won't upscale images
if ($width <= $original_image->width() && $height <= $original_image->height()) {
$current_image = $original_image->size($width, $height, ['upscaling' => false, 'suffix' => $suffix]);
$srcset[] = $current_image->url() . " {$width}w";
}
}
$srcset = implode(', ', $srcset);
return $srcset;
}
/**
* Shortcut for getSrcset that only takes a width parameter.
* Height is automatically generated based on the aspect ratio of the passed image.
*
* @param Pageimage $img The base image. Must be passed in the largest size available.
* @param int|null $standard_width The standard width for this image. Use NULL to use the inherent size of the passed image.
* @param array $variant_factors The multiplication factors for the alternate resolutions.
* @return string
*/
public function getSrcsetByWidth(
Pageimage $img,
?int $standard_width = null,
array $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]
): string {
if (empty($standard_width)) {
$standard_width = $img->width();
}
// automatically fill the height parameter based
// on the aspect ratio of the passed image
$factor = $img->height() / $img->width();
$standard_height = ceil($factor * $standard_width);
return self::getSrcset(
$img,
$standard_width,
$standard_height,
$variant_factors
);
}
/**
* Shortcut for getSrcset that only takes a height parameter.
* Width is automatically generated based on the aspect ratio of the passed image.
*
* @param Pageimage $img The base image. Must be passed in the largest size available.
* @param int|null $standard_height The standard height for this image. Use NULL to use the inherent size of the passed image.
* @param array $variant_factors The multiplication factors for the alternate resolutions.
* @return string
*/
public function getSrcsetByHeight(
Pageimage $img,
?int $standard_height = null,
array $variant_factors = [0.25, 0.5, 0.75, 1, 1.5, 2]
): string {
// automatically fill the width parameter based
// on the aspect ratio of the passed image
if (empty($standard_height)) {
$standard_height = $img->height();
}
$factor = $img->width() / $img->height();
$standard_width = ceil($factor * $standard_height);
return self::getSrcset(
$img,
$standard_width,
$standard_height,
$variant_factors
);
}
}
{# This assumes the responsive-image.twig file lives inside a 'blocks' folder inside your twig directory, and a sidebar_image field on your page #}
{{ include(
'blocks/image-responsive.twig',
{
image: page.sidebar_image,
classes: ['sidebar-image', 'img-fluid'],
width: 510,
sizes: ['(min-width: 1200px) 350px', '(min-width: 992px) 290px', '(min-width: 768px) 330px', '(min-width: 576px) 510px', 'calc(100vw - 30px)']
},
with_context = false
) }}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment