DEV Community

Cover image for Statamic Image Partial
Daniel Wentsch
Daniel Wentsch

Posted on

Statamic Image Partial

Goal: Generate multiple image sizes within <source> tags using provided values for media queries and image dimensions. Serve them as both the original file format and as wepb for supporting browsers.

Initial situation:

This is a common pattern I use to create an optimal sized and formatted image, that will be cropped around an editor-defined focus point. However it's super annoying having to write this for each and every component with only the numbers changing from one to another:

<picture class="block w-full" >
    <source
        media="(min-width: 1280px)"
        srcset="{{ glide:image width="1120" height="1120" format="webp" fit="crop_focal" }}"
        type="image/webp"
        >
    <source
        media="(min-width: 1280px)"
        srcset="{{ glide:image width="1120" height="1120" fit="crop_focal" }}"
        type="{{ image.mime_type }}"
    >
    <source
        media="(min-width: 980px)"
        srcset="{{ glide:image width="900" height="520" format="webp" fit="crop_focal" }}"
        type="image/webp">
    <source
        media="(min-width: 980px)"        
                srcset="{{ glide:image width="900" height="520" fit="crop_focal" }}"
        type="{{ image.mime_type }}"
        >
    <source
        data-srcset="{{ glide:image width="450" height="260" format="webp" fit="crop_focal" }}"
        type="image/webp"
    >
    <img
        class="block object-cover w-full h-auto lazyload"
        src="{{ glide:image width="450" height="260" fit="crop_focal" }}"
        alt="{{ title }}"
        height="520"
        width="900"
    >
</picture>
Enter fullscreen mode Exit fullscreen mode

💡 Idea

A partial could take the following arguments:

{{ picture :viewports="2000['1600', '800'], 2000['800', '600'], DEFAULT['400', '300']" }}
Enter fullscreen mode Exit fullscreen mode

Creating and passing an array on the fly inside an attribute doesn't work in antlers. It could be passed as a comma separated string and then exploded using the explode modifier.

But wait, I just recently learned that we can use YAML frontmatter right within antlers templates \o/.

It seems way cleaner to define viewports with their respective images sizes in Frontmatter, which can then be passed as an array. No need for exploding strings at all:

---
viewports:
  2000: [1600, 800]
  1000: [800, 600]
  'DEFAULT': [400, 300]
---

{{ partial:components/test :viewports="view:viewports" }}
Enter fullscreen mode Exit fullscreen mode

This can be iterated over with foreach to generate a <source> tag with the media_query value being used as the value for min-width and the nested

{{ foreach:viewports as="media_query|sizes" }}
    <p>Media Query: {{ media_query }}</p>
    <p>Width: {{ sizes:0 }}</p>
    <p>Height: {{ sizes:1 }}</p>
{{ /foreach:viewports }}
Enter fullscreen mode Exit fullscreen mode

Better Solution

YAML's nested object syntax makes this look a bit nicer:

---
viewports:
  2000: {'w': 1600, 'h': 800}
  1000: {'w': 800, 'h': 600}
  'DEFAULT': {'w': 400, 'h': 300}
---

{{# … #}}

{{ image }}
    {{ partial:components/picture_cropped
        :viewports="view:viewports"
      :image="image"
        lazy="true"
    }}
{{ /image }} 
Enter fullscreen mode Exit fullscreen mode

This can be iterated over nicely with {{ foreach:viewports as "media_query|dimensions"}}:

{{ if image }}
    <picture>
        {{ asset :url="image" }}
            {{ if extension == 'svg' || extension == 'gif' }}
                <img
                    class="{{ class }}"
                    src="{{ url }}"
                    alt="{{ alt }}"
                />
            {{ else }}
                {{ foreach:viewports as="media_query|dimensions" }}
                    {{ if media_query != 'DEFAULT'}}
                            <source
                                media="(min-width: {{ media_query }}px)"
                                srcset="{{ glide:image :width="dimensions:w" :height="dimensions:h" format="webp" fit="crop_focal" }}"
                                type="image/webp"
                            />
                            <source
                                media="(min-width: {{ media_query }})px"
                                srcset="{{ glide:image :width="dimensions:w" :height="dimensions:h" fit="crop_focal" }}"
                                type="{{ image.mime_type }}"
                            />

                        {{ else }}
                            <source
                                srcset="{{ glide:image :width="dimensions:w" :height="dimensions:h" format="webp" fit="crop_focal" }}"
                                type="image/webp"
                            />
                            <img
                                class="block object-cover w-full h-auto {{ class }}"
                                src="{{ glide:image :width="dimensions:w"   :height="dimensions:h" fit="crop_focal" }}"
                                alt="{{ alt }}"
                                height="{{ dimensions:h }}"
                                width="{{ dimensions:w }}"
                                {{ if lazy }}
                                    loading="lazy"
                                {{ /if }}
                            />
                        {{ /if }}
                {{ /foreach:viewports }}
            {{ /if }}
        {{ /asset }}
    </picture>
{{ /if }}
Enter fullscreen mode Exit fullscreen mode

Actual result when invoked:

image

Top comments (0)