DEV Community

Cover image for Statamic Image Partial
Daniel Wentsch
Daniel Wentsch

Posted on

3 1

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

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up