Thumb array rule doesn't work

The template I am using has a media query for @media (min-width: 768px) { .md\:w-2\/3 { width: 66.666667%; } } and this is the element I came up with:

​<picture>
  <source srcset="<?= $image->thumb(['width'   => 400, 'quality' => 80, 'format' => 'avif'])->url() ?> 400w, <?= $image->thumb(['width'   => 800, 'quality' => 80, 'format' => 'avif'])->url() ?> 800w, <?= $image->thumb(['width'   => 1200, 'quality' => 80, 'format' => 'avif'])->url() ?> 1200w, <?= $image->thumb(['width'   => 1600, 'quality' => 80, 'format' => 'avif'])->url() ?> 1600w" sizes="(max-width: 767px) calc(0.8 * 100vw), calc(0.61 * 100vw)" type="image/avif">
  <source srcset="<?= $image->thumb(['width'   => 400, 'quality' => 80, 'format' => 'webp'])->url() ?> 400w, <?= $image->thumb(['width'   => 800, 'quality' => 80, 'format' => 'webp'])->url() ?> 800w, <?= $image->thumb(['width'   => 1200, 'quality' => 80, 'format' => 'webp'])->url() ?> 1200w, <?= $image->thumb(['width'   => 1600, 'quality' => 80, 'format' => 'webp'])->url() ?> 1600w" sizes="(max-width: 767px) calc(0.8 * 100vw), calc(0.61 * 100vw)" type="image/webp">
  <img src="<?= $image->url() ?>" srcset="<?= $image->thumb(['width'   => 400, 'quality' => 80])->url() ?> 400w, <?= $image->thumb(['width'   => 800, 'quality' => 80])->url() ?> 800w, <?= $image->thumb(['width'   => 1200, 'quality' => 80])->url() ?> 1200w, <?= $image->thumb(['width'   => 1600, 'quality' => 80])->url() ?> 1600w" sizes="(max-width: 767px) calc(0.8 * 100vw), calc(0.61 * 100vw)" decoding="async" loading="lazy" alt="" style="background-size: cover; background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns=\'http%3A//www.w3.org/2000/svg\' xmlns%3Axlink=\'http%3A//www.w3.org/1999/xlink\' viewBox=\'0 0 1280 853\'%3E%3Cfilter id=\'b\' color-interpolation-filters=\'sRGB\'%3E%3CfeGaussianBlur stdDeviation=\'.5\'%3E%3C/feGaussianBlur%3E%3CfeComponentTransfer%3E%3CfeFuncA type=\'discrete\' tableValues=\'1 1\'%3E%3C/feFuncA%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter=\'url(%23b)\' x=\'0\' y=\'0\' height=\'100%25\' width=\'100%25\' xlink%3Ahref=\'data%3Aimage/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAGCAIAAACepSOSAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAs0lEQVQI1wGoAFf/AImSoJSer5yjs52ktp2luJuluKOpuJefsoCNowB+kKaOm66grL+krsCnsMGrt8m1u8mzt8OVoLIAhJqzjZ2tnLLLnLHJp7fNmpyjqbPCqLrRjqO7AIeUn5ultaWtt56msaSnroZyY4mBgLq7wY6TmwCRfk2Pf1uzm2WulV+xmV6rmGyQfFm3nWSBcEIAfm46jX1FkH5Djn5AmodGo49MopBLlIRBfG8yj/dfjF5frTUAAAAASUVORK5CYII=\'%3E%3C/image%3E%3C/svg%3E'); ">
</picture>

The problem with this code is that always the highest width is provided even if I am emulating a small device screen/viewport with the browser developer tools. What am I missing?

What @texnixe wrote above…

In the mentioned documentation it sounds as if my approach should work:

The srcset attribute is used to offer list of possible images based on size .

It is composed of a comma-separated list of image descriptors. Each image descriptor is composed of a URL of the image, and either…

  • a width descriptor , followed by a w (such as 300w );
    OR
  • a pixel density descriptor , followed by an x (such as 2x ) to serve a high-res image for high-DPI screens.

Also this documentation lists this but I get confused by the many combination possibilities.
Is there maybe any plugin that I can just use that does the same? I just want to make sure that always the best possible image (format/size etc.) is delivered.

Yes, I saw that as well but couldn’t really get it to work.

I might be talking out of my hat, but I believe this is actually triggered by device width, not viewport width. To truly see it, you need to actually look on a small screen device. Not sure what server you are using but if its something with BroswerSync, it will give you an external URL you can pull up on a phone / tablet. From there you can remote debug the phones browser from Chrome or Safari on your desktop. Google can help you with how to do that.

So it works actually differently from image srcset?

I believe so, yes. I think its not working because with emulating mobiles in the browser, your device width is still the width of the computer monitor, triggering the highest res image. You need to use Apple simulator that comes with xcode, or the the simulator that comes with Android studio and remote debug to do a real test.

Or remote debug a real phone / tablet via browsersync.

With my mentioned code it seems to work somehow but the behaviour is really weird.
I get multiple kinds of image sizes generated by Kirby (at least based on the file name) but in the end they are all the same.
I found this out with a test from web.dev. One example URL:

https://domain.tld/kirbytest/media/pages/blog/vierter-beitrag/8a7534e6bb-1628195522/img-20170217-122206-480x-q48.avif

Based on the file name I would assume it is an AVIF image with 480px width and a quality level of 48 but instead it is the original jpeg in full quality and size:

Whats the order of the sources in your picture tag? Browsers read the first format they understand. You need to list out the Avif sources first (quite exoctic still) Webp next (reasonable support), and Jpeg last (supported everywhere).

It is in exactly that order: Thumb array rule doesn't work - #21 by Peleke

Hmmm tricky. With Kirby 3.6, the webp and avif files are file versions i think which end up in the media folder. With my webp plugin, i create webp files in the content folders as files in their own right.

For what its worth, i loop over an array of sizes and variants to get the picture tag Maybe you can adapt that.

That feature was improved by my good friend @nilshoerrmann maybe he can help here.

1 Like

I saw it and have not enough PHP knowledge to extend it to this use case.
Could the behavior also be a bug of the alpha 3?

Have you tested if your imagemagic version actually supports those formats? e.g. tested on the command line?

1 Like

Indeed that was the case. Otherwise the following code now seems to work correctly:

​<picture>
  <source srcset="<?= $image->thumb(['width'   => 400, 'quality' => 48, 'format' => 'avif'])->url() ?> 400w, <?= $image->thumb(['width'   => 800, 'quality' => 48, 'format' => 'avif'])->url() ?> 800w, <?= $image->thumb(['width'   => 1200, 'quality' => 48, 'format' => 'avif'])->url() ?> 1200w, <?= $image->thumb(['width'   => 1600, 'quality' => 48, 'format' => 'avif'])->url() ?> 1600w" sizes="(max-width: 767px) calc(0.8 * 100vw), calc(0.61 * 100vw)" type="image/avif">
  <source srcset="<?= $image->thumb(['width'   => 400, 'quality' => 55, 'format' => 'webp'])->url() ?> 400w, <?= $image->thumb(['width'   => 800, 'quality' => 55, 'format' => 'webp'])->url() ?> 800w, <?= $image->thumb(['width'   => 1200, 'quality' => 55, 'format' => 'webp'])->url() ?> 1200w, <?= $image->thumb(['width'   => 1600, 'quality' => 55, 'format' => 'webp'])->url() ?> 1600w" sizes="(max-width: 767px) calc(0.8 * 100vw), calc(0.61 * 100vw)" type="image/webp">
  <img src="<?= $image->url() ?>" height="<?= $image->height() ?>" width="<?= $image->width() ?>" srcset="<?= $image->thumb(['width'   => 400, 'quality' => 50])->url() ?> 400w, <?= $image->thumb(['width'   => 800, 'quality' => 50])->url() ?> 800w, <?= $image->thumb(['width'   => 1200, 'quality' => 50])->url() ?> 1200w, <?= $image->thumb(['width'   => 1600, 'quality' => 50])->url() ?> 1600w" sizes="(max-width: 767px) calc(0.8 * 100vw), calc(0.61 * 100vw)" decoding="async" loading="lazy" alt="<?=$image->alt()?>" style="background-size: cover; background-image: url('data:image/svg+xml;charset=utf-8,%3Csvg xmlns=\'http%3A//www.w3.org/2000/svg\' xmlns%3Axlink=\'http%3A//www.w3.org/1999/xlink\' viewBox=\'0 0 1280 853\'%3E%3Cfilter id=\'b\' color-interpolation-filters=\'sRGB\'%3E%3CfeGaussianBlur stdDeviation=\'.5\'%3E%3C/feGaussianBlur%3E%3CfeComponentTransfer%3E%3CfeFuncA type=\'discrete\' tableValues=\'1 1\'%3E%3C/feFuncA%3E%3C/feComponentTransfer%3E%3C/filter%3E%3Cimage filter=\'url(%23b)\' x=\'0\' y=\'0\' height=\'100%25\' width=\'100%25\' xlink%3Ahref=\'data%3Aimage/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAGCAIAAACepSOSAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAs0lEQVQI1wGoAFf/AImSoJSer5yjs52ktp2luJuluKOpuJefsoCNowB+kKaOm66grL+krsCnsMGrt8m1u8mzt8OVoLIAhJqzjZ2tnLLLnLHJp7fNmpyjqbPCqLrRjqO7AIeUn5ultaWtt56msaSnroZyY4mBgLq7wY6TmwCRfk2Pf1uzm2WulV+xmV6rmGyQfFm3nWSBcEIAfm46jX1FkH5Djn5AmodGo49MopBLlIRBfG8yj/dfjF5frTUAAAAASUVORK5CYII=\'%3E%3C/image%3E%3C/svg%3E'); ">
</picture>

I wish that this kind of feature will be integrated natively soon to save the hassle of creating it manually.
Is there any way to use this snippet for rendered articles as well (everything that is consumed from a content txt)?

What do you mean? Automatically integrating responsive images without knowing where these images are used and how doesn’t really make sense.

I would like to use the same logic in rendered block content for example.
So if I add an image in a block within the panel the template will render it as AVIF or WEBP in fitting sizes.

You can adapt the relevant block snippets to your liking.

1 Like

I didn’t go through the whole discussion, but what Peleke tries to achieve in the beginning i did with the following:

  'thumbs' => [
    'driver' => 'im',
    'srcsets' => [
      'default' => [
        '320w' => ['width' => 320, 'quality' => 100],
        '528w' => ['width' => 528, 'quality' => 80],
        '1000w' => ['width' => 1000, 'quality' => 90],
      ],
      'default_webp' => [
        '320w' => ['width' => 320, 'quality' => 100, 'format' => 'webp'],
        '528w' => ['width' => 528, 'quality' => 80, 'format' => 'webp'],
        '1000w' => ['width' => 1000, 'quality' => 90, 'format' => 'webp'],
      ],
      'default_avif' => [
        '320w' => ['width' => 320, 'quality' => 100, 'format' => 'avif'],
        '528w' => ['width' => 528, 'quality' => 80, 'format' => 'avif'],
        '1000w' => ['width' => 1000, 'quality' => 90, 'format' => 'avif'],
      ]
    ],
    'presets' => [
      'product' => [
        'width' => 330,
        'quality' => 80,
        'format' => 'jpeg'
      ]
    ]
  ],

And then combined with a pageMethod

    'uiPicture' => function ($imageFile, $sizes = null, $loading = 'lazy') {
      $variants = ['avif', 'webp', 'jpeg'];

      $src = $imageFile->thumb('product')->url();
      $alt = $imageFile->alt();
      $width = $imageFile->width();
      $height = $imageFile->height();

      if (!$sizes) {
        $sizes = "(min-width: 528px) 528px, 100vw";
      }

      $sources = [];
      foreach ($variants as $variant) {
        $presetName = $variant === 'jpeg' ? 'default' : 'default_' . $variant;
        array_push($sources, [
          "type" => 'image/' . $variant,
          "srcset" => $imageFile->srcset($presetName)
        ]);
      }

      return compact('src', 'alt', 'loading', 'sizes', 'width', 'height', 'sources');
    },

That gives me everything I need to build the <picture> tag.

1 Like

Thanks for the input. Can you please also show an example of the picture tag that you use (or the solution you use to render the UiPicture method) then to embed an image?

@Peleke I do everything in Twig so it might be a bit of a stretch for you to understand it if you are not familiar with Twig.

But here is the Twig macro I use in one project.

{% macro picture(props) %}
  <picture class="c-picture">
    {% if props.sources and (props.sources|length) > 0 %}
      {% for source in props.sources %}
        <source type="{{ source.type }}" srcset="{{ source.srcset }}" sizes="{{ props.sizes|default('') }}" />
      {% endfor %}
    {% endif %}
    <img src="{{ props.src }}"
      alt="{{ props.alt }}"
      width="{{ props.width }}"
      height="{{ props.height }}"
      loading="{{ props.loading|default('eager') }}" />
  </picture>
{% endmacro %}

(Where props is the output of the uiPicture method)

In another project, I have a more advanced form of that, which includes the media tag on the sources. But anyway, you might get the idea.

I find this whole approach excellent now as Kirby has native support for webp and avif. Before, with the webp plugin, the server used to crash as I have a high amount of different image sizes for different situations, leading to the generation of around 20 sizes per image.

Still, there would probably be a better solution for defining the sizes. I’m just a poor HTML/JavaScript programmer (not much PHP experience), which leads to a very pragmatic config with a lot of repetition.

  'thumbs' => [
    'driver' => 'im',
    'presets' => [
      'thumb' => ['width' => 232, 'height' => 232, 'crop' => true, 'quality' => 100],
      'alternative_thumb' => ['width' => 825, 'height' => 564, 'quality' => 100, 'crop' => true],
      'leaderboard' => ['width' => 1530, 'height' => 470, 'crop' => true, 'quality' => 80],
      'portrait' => ['width' => 362, 'height' => 640, 'crop' => true, 'quality' => 80],
    ],
    'srcsets' => [
      'default' => [
        '150w' => ['width' => 125, 'quality' => 100],
        '256w' => ['width' => 256, 'quality' => 100],
        '320w' => ['width' => 320, 'quality' => 100],
        '768w' => ['width' => 768, 'quality' => 80],
        '1560w' => ['width' => 1440, 'quality' => 90],
        '2000w' => ['width' => 2000, 'quality' => 80],
        '3000w' => ['width' => 3000, 'quality' => 60]
      ],
      'default_webp' => [
        '150w' => ['width' => 125, 'quality' => 100, 'format' => 'webp'],
        '256w' => ['width' => 256, 'quality' => 100, 'format' => 'webp'],
        '320w' => ['width' => 320, 'quality' => 100, 'format' => 'webp'],
        '768w' => ['width' => 768, 'quality' => 80, 'format' => 'webp'],
        '1560w' => ['width' => 1440, 'quality' => 90, 'format' => 'webp'],
        '2000w' => ['width' => 2000, 'quality' => 80, 'format' => 'webp'],
        '3000w' => ['width' => 3000, 'quality' => 60, 'format' => 'webp']
      ],
      'default_avif' => [
        '150w' => ['width' => 125, 'quality' => 100, 'format' => 'avif'],
        '256w' => ['width' => 256, 'quality' => 100, 'format' => 'avif'],
        '320w' => ['width' => 320, 'quality' => 100, 'format' => 'avif'],
        '768w' => ['width' => 768, 'quality' => 80, 'format' => 'avif'],
        '1560w' => ['width' => 1440, 'quality' => 90, 'format' => 'avif'],
        '2000w' => ['width' => 2000, 'quality' => 80, 'format' => 'avif'],
        '3000w' => ['width' => 3000, 'quality' => 60, 'format' => 'avif']
      ],
      'thumb' => [
        '232w' => ['width' => 232, 'height' => 232, 'quality' => 100, 'crop' => true],
        '464w' => ['width' => 464, 'height' => 464, 'quality' => 80, 'crop' => true]
      ],
      'thumb_webp' => [
        '232w' => ['width' => 232, 'height' => 232, 'quality' => 100, 'crop' => true, 'format' => 'webp'],
        '464w' => ['width' => 464, 'height' => 464, 'quality' => 80, 'crop' => true, 'format' => 'webp']
      ],
      'thumb_avif' => [
        '232w' => ['width' => 232, 'height' => 232, 'quality' => 100, 'crop' => true, 'format' => 'avif'],
        '464w' => ['width' => 464, 'height' => 464, 'quality' => 80, 'crop' => true, 'format' => 'avif']
      ],
      // This is a 1.4/1 format
      'alternative_thumb' => [
        '232w' => ['width' => 232, 'height' => 158, 'quality' => 100, 'crop' => true],
        '464w' => ['width' => 464, 'height' => 318, 'quality' => 80, 'crop' => true],
        '825w' => ['width' => 825, 'height' => 564, 'quality' => 80, 'crop' => true],
        '1650w' => ['width' => 1650, 'height' => 1328, 'quality' => 80, 'crop' => true]
      ],
      'alternative_thumb_webp' => [
        '232w' => ['width' => 232, 'height' => 158, 'quality' => 100, 'crop' => true, 'format' => 'webp'],
        '464w' => ['width' => 464, 'height' => 318, 'quality' => 80, 'crop' => true, 'format' => 'webp'],
        '825w' => ['width' => 825, 'height' => 564, 'quality' => 80, 'crop' => true, 'format' => 'webp'],
        '1650w' => ['width' => 1650, 'height' => 1328, 'quality' => 80, 'crop' => true, 'format' => 'webp']
      ],
      'alternative_thumb_avif' => [
        '232w' => ['width' => 232, 'height' => 158, 'quality' => 100, 'crop' => true, 'format' => 'avif'],
        '464w' => ['width' => 464, 'height' => 318, 'quality' => 80, 'crop' => true, 'format' => 'avif'],
        '825w' => ['width' => 825, 'height' => 564, 'quality' => 80, 'crop' => true, 'format' => 'avif'],
        '1650w' => ['width' => 1650, 'height' => 1328, 'quality' => 80, 'crop' => true, 'format' => 'avif']
      ],
      'leaderboard' => [
        '1530w' => ['width' => 1530, 'height' => 470, 'crop' => true, 'quality' => 80],
        '3050w' => ['width' => 3050, 'height' => 940, 'crop' => true, 'quality' => 60]
      ],
      'leaderboard_webp' => [
        '1530w' => ['width' => 1530, 'height' => 470, 'crop' => true, 'quality' => 80, 'format' => 'webp'],
        '3050w' => ['width' => 3050, 'height' => 940, 'crop' => true, 'quality' => 60, 'format' => 'webp']
      ],
      'leaderboard_avif' => [
        '1530w' => ['width' => 1530, 'height' => 470, 'crop' => true, 'quality' => 80, 'format' => 'avif'],
        '3050w' => ['width' => 3050, 'height' => 940, 'crop' => true, 'quality' => 60, 'format' => 'avif']
      ],
      'portrait' => [
        '362w' => ['width' => 362, 'height' => 640, 'crop' => true, 'quality' => 100],
        '724w' => ['width' => 724, 'height' => 1280, 'crop' => true, 'quality' => 60],
      ],
      'portrait_webp' => [
        '362w' => ['width' => 362, 'height' => 640, 'crop' => true, 'quality' => 100, 'format' => 'webp'],
        '724w' => ['width' => 724, 'height' => 1280, 'crop' => true, 'quality' => 60, 'format' => 'webp'],
      ],
      'portrait_avif' => [
        '362w' => ['width' => 362, 'height' => 640, 'crop' => true, 'quality' => 100, 'format' => 'avif'],
        '724w' => ['width' => 724, 'height' => 1280, 'crop' => true, 'quality' => 60, 'format' => 'avif'],
      ]
    ],
  ],
2 Likes