Statamic Image Partial
Tags: statamic, frontend, templating, performance
Published: 2021-09-18 18:40:51

**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:

```html
<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>
```

## 💡 Idea

A partial could take the following arguments:

```html
{{ picture :viewports="2000['1600', '800'], 2000['800', '600'], DEFAULT['400', '300']" }}
```

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](https://statamic.dev/modifiers/explode).

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:

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

{{ partial:components/test :viewports="view:viewports" }}
```

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  

```html
{{ foreach:viewports as="media_query|sizes" }}
    <p>Media Query: {{ media_query }}</p>
    <p>Width: {{ sizes:0 }}</p>
    <p>Height: {{ sizes:1 }}</p>
{{ /foreach:viewports }}
```

## Better Solution

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

```json
---
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 }} 
```

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

```json
{{ 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 }}
```

## Actual result when invoked:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y00jnmmh0b9powimpaeu.png)