1

I come from a WordPress background, so I'm attempting to create responsive images the way I'm accustomed to with WordPress. Perhaps that's the wrong approach, I'm not sure.

In WordPress I would define five images size: thumbnail, medium, medium_large, large, XL and I would specify a width for each. Typically I would define the widths as 414, 768, 1024, 1600 and 1920 pixels respectively. For the sizes attribute I would specify this manually on an image-by-image basis.

Outputting an image would look like this:

<img src="<?php echo wp_get_attachment_image_url( $attachment_id, 'large' ); ?>"
     srcset="<?php echo wp_get_attachment_image_srcset( $attachment_id ); ?>" />

The above would output all 5 image sizes to the srcset attribute.

So, back to Craft. I have setup 5 asset transforms under Settings using the same handles as above. I believe you can also set these up progmatically too.

I can then output each of my image sizes in the following way:

{% set image = entry.aboutImage.withTransforms(['thumbnail', 'medium', 'mediumLarge', 'large', 'xl']).first() %}

<img src="{{ image.getUrl('medium') }}" alt=""
     srcset="{{ image.getUrl('xl') }} {{ image.getWidth('xl') }}w,
             {{ image.getUrl('large') }} {{ image.getWidth('large') }}w,
             {{ image.getUrl('mediumLarge') }} {{ image.getWidth('mediumLarge') }}w,
             {{ image.getUrl('medium') }} {{ image.getWidth('medium') }}w,
             {{ image.getUrl('thumbnail') }} {{ image.getWidth('thumbnail') }}w"
     width="{{ image.getWidth() }}" height="{{ image.getHeight() }}" />

Whilst this works okay, it's kind of cumbersome to repeat this for every image. Also, if the user uploads a very small image then I'm guessing it won't be available in all sizes.

I noticed that there is also this method:

{{ asset.getImg({ width: 300, height: 300 }, ['1.5x', '2x', '3x']) }}

However I'd like to do the following, which doesn't work:

{{ asset.getImg({ width: 300, height: 300 }, ['thumbnail', 'medium', 'mediumLarge', 'large', 'xl']) }}

I've read about macros but so far haven't really been able to fathom out how I'd achieve the above using a macro, or it it's necessary.

2 Answers 2

3

You can write a function, a utility template or a macro to make repetitive tasks like generating responsive images easier. I usually prefer templates, because they're easy to extend and they can take a lot of optional arguments without the user needing to pass all of them.

Building your own utility templates is a good time investments, since you can create a set of parameters / options into the template that you can use wherever you want to display images in your templates.

For example, here's a simple responsive image template that will take an image asset and a list of transforms and output an <img> tag:

{# utils/responsive-image.twig #}
{%- set transforms = transforms|default([]) -%}
{%- set transformedImages = transforms|map(transform => image.copyWithTransform(transform)) -%}
{%- set srcset = transformedImages|map(image => "#{image.getUrl()} #{image.width}w") -%}
{%- set attributes = attributes ?? {} -%}

<img {{ attr(attributes|merge({ srcset: srcset|join(', '), })) }}>

Since this template accepts a list of transforms, you can pass it any parameters that are accepted by Asset.getUrl(). So both named transforms and hashes will work:

{% set image = entry.header_image.one() %}
{{ include('utils/responsive-image', {
    image: image,
    transforms: [{ width: 300, height: 300 }, { width: 600, height: 600 }],
}, with_context = false) }}
{% set image = entry.header_image.one() %}
{{ include('utils/responsive-image', {
    image: image,
    transforms: ['thumbnail', 'medium', 'mediumLarge'],
}, with_context = false) }}

Note the utility template also includes an optional parameter to specify additional attributes for the <img> tag. Here's a complete example:

{% set image = entry.header_image.one() %}
{{ include('utils/responsive-image', {
    image: image,
    transforms: [
        { width: 300, height: 300, mode: 'crop', position: 'top-center'},
        { width: 600, height: 600 }
    ],
    attributes: {
        width: 300,
        height: 300,
        srcset: '(min-width: 300px) 300px, 100vw',
        class: 'my-responsive-image',
    },
}, with_context = false) }}

Once you have the basic setup in place, you can expand your utility template to handle more repetitive tasks for you. For example, my utility template for responsive images can:

  • Generate a <picture> with multiple sources.
  • Automatically generate alternative <source> tags with WebP images.
  • Apply a fixed aspect ratio to all transforms.
  • Handle SVGs gracefully.
  • Take the alt text from the asset's alt field, if it exists.
  • … and much more.
7
  • Thanks for this, I'd be very interested to see the full version of your img template if you'd like to share it... Commented Sep 27, 2021 at 9:50
  • 1
    @JamesSmith Sorry, I developed that template for work, so I can't share it in full. But its a good exercise to develop your own starting with something small like my example here and then add features/parameters as they come up. It also really depends on how you build your templates and how you usually incorporate images, so what you end up with could focus on totally different aspects than my utility template!
    – MoritzLost
    Commented Sep 29, 2021 at 9:10
  • This is really cool @MoritzLost - Can I ask, how would you place the mode? As im trying to crop with a specific focal point but am not clear on how to achieve this outcome
    – Wally
    Commented Jun 16, 2022 at 1:42
  • 1
    @Wally If you use the transforms parameter as in the starter template above, you can just include crop and mode in the transform definition. I've updated the usage example code to include those parameters. In the full version of the utility template I use for my projects, both of those can be passed as optional parameters as well and are automatically merged into the transforms if passed.
    – MoritzLost
    Commented Jun 16, 2022 at 18:03
  • 1
    @MoritzLost amazing like always!
    – Wally
    Commented Jun 17, 2022 at 1:55
1

Craft has a newer asset method called getSrcset which should be close to what you need. Here's the code example from the docs:

{% do asset.setTransform({ width: 300, height: 300 }) %}
{{ tag('img', {
  src: asset.url,
  width: asset.width,
  height: asset.height,
  srcset: asset.getSrcset(['1.5x', '2x', '3x']),
  alt: asset.title,
}) }}

You can also skip the transform and use explicit widths (rather than the 1x, 2x variants) like this:

{{ tag('img', {
  src: asset.url,
  width: asset.width,
  height: asset.height,
  srcSet: asset.getSrcset(['414w', '768w', '1024w', '1600w', '1920w']),
  alt: asset.title,
}) }}

Assuming you already have transforms set for those sizes, it should use the same rendered images.

And if you wanted to skip the src tag for some reason, you could write it like this:

<img src="asset.url"
  width="asset.width"
  height="asset.height"
  srcSet="asset.getSrcset(['414w', '768w', '1024w', '1600w', '1920w'])"
  alt="asset.title"
/>
2
  • That's useful, and close to what I'm looking for. The downside is if I change my 414 transform to say 480, then I have to find and replace all instances of it, rather than being able to reference it by name, i.e. thumbnail
    – MrCarrot
    Commented Sep 29, 2021 at 14:09
  • Yeah we don’t use many named transforms so that typically hasn’t been a problem in our case. We just generate them on the fly whenever the template is rendered and then the transformed assets are cached for future use. Commented Sep 29, 2021 at 19:06

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.