In Statamic it's pretty easy to handle responsive images using Glide and Antlers. I initially wrote this post over two years ago for Statamic v2 and it's proven to be the most visited page of my website. For my new website I decided to give it an update. Here we go.
Update October 24th 2022
I recently changed my approach to enable on the fly aspect ratio cropping. Check it out in this PR
My Starter Kit Peak contains full blown responsive images support. It doesn't use any addons or custom code, it's just native Antlers and Statamic. This is how I prefer to develop my websites as it leaves me in control and on a per site basis I can tailor everything to different demands.
Making sure you deliver the right assets to the client helps performance and positioning within search engines. It's important for a good user experience and fast loading times. So how can we do this? Let's start by configuring Statamic.
Configuring Statamic
Let's configure Statamic so that it automatically generates asset presets when a user uploads an image to the control panel. We want Statamic to generate different sizes for each image so we can serve users the lowest image resolution possible depending on their device without sacrificing image quality. Statamic uses the Glide library for this. Luckily Glide can also convert images to WebP. So let’s throw that in their as well.
Note: I use
Imagick
as an image processor instead ofGD
. I've found it produces better results with smaller files.
For all this magic you need to to edit your config/statamic/assets.php
file. This is my current setup.
1'presets' => [ 2 'xs-webp' => ['w' => 320, 'h' => 10000, 'q' => 85, 'fit' => 'contain', 'fm' => 'webp'], 3 'sm-webp' => ['w' => 480, 'h' => 10000, 'q' => 85, 'fit' => 'contain', 'fm' => 'webp'], 4 'md-webp' => ['w' => 768, 'h' => 10000, 'q' => 85, 'fit' => 'contain', 'fm' => 'webp'], 5 'lg-webp' => ['w' => 1280, 'h' => 10000, 'q' => 85, 'fit' => 'contain', 'fm' => 'webp'], 6 'xl-webp' => ['w' => 1440, 'h' => 10000, 'q' => 95, 'fit' => 'contain', 'fm' => 'webp'], 7 '2xl-webp' => ['w' => 1680, 'h' => 10000, 'q' => 95, 'fit' => 'contain', 'fm' => 'webp'], 8 'xs' => ['w' => 320, 'h' => 10000, 'q' => 85, 'fit' => 'contain'], 9 'sm' => ['w' => 480, 'h' => 10000, 'q' => 85, 'fit' => 'contain'],10 'md' => ['w' => 768, 'h' => 10000, 'q' => 85, 'fit' => 'contain'],11 'lg' => ['w' => 1280, 'h' => 10000, 'q' => 85, 'fit' => 'contain'],12 'xl' => ['w' => 1440, 'h' => 10000, 'q' => 95, 'fit' => 'contain'],13 '2xl' => ['w' => 1680, 'h' => 10000, 'q' => 95, 'fit' => 'contain'],14],
The Statamic asset configuration file. These are the presets that get generated for every uploaded asset.
Having this in place, make sure you add the following to your deploy script (check out an example deploy script) and run it if you already have some assets uploaded to your asset container.
php please assets:generate-presets
This will make Statamic generate all the different asset versions we want. Running this after deployment makes sure that the images are always there should the image cache be cleared. If you don’t pre generate all the asset variants, Statamic will do this when the first visitor drops by. This will result in a terrible page load for this one unlucky person. After that the presets will be cached.
Personally I use Redis for queue management so users won’t notice any slowness during the upload process. It basically moves tasks to a queue in the background. It might take longer to complete but your server will still respond to other requests.
The Antlers <picture> partial
Now we’ve got our assets prepared, it’s time to make the _picture.antlers.html
partial.
1{{# 2 @name Picture 3 @desc The picture component. A responsive imageset in a picture element. See `resources/views/components/_figure.antlers.html` for an example on how to use this. 4#}} 5 6<!-- /components/_picture.antlers.html --> 7{{ if image }} 8 <picture> 9 {{ asset :url="image" }}10 {{ if extension == 'svg' || extension == 'gif' }}11 <img class="{{ class }}" src="{{ url }}" alt="{{ alt }}" />12 {{ else }}13 <source14 srcset="15 {{ glide:image preset='xs-webp' }} 320w,16 {{ glide:image preset='sm-webp' }} 480w,17 {{ glide:image preset='md-webp' }} 768w,18 {{ glide:image preset='lg-webp' }} 1280w,19 {{ glide:image preset='xl-webp' }} 1440w,20 {{ glide:image preset='2xl-webp' }} 1680w"21 sizes="{{ sizes }}"22 type="image/webp"23 >24 <source25 srcset="26 {{ glide:image preset='xs' }} 320w,27 {{ glide:image preset='sm' }} 480w,28 {{ glide:image preset='md' }} 768w,29 {{ glide:image preset='lg' }} 1280w,30 {{ glide:image preset='xl' }} 1440w,31 {{ glide:image preset='2xl' }} 1680w"32 sizes="{{ sizes }}"33 type="{{ image.mime_type }}"34 >35 <img36 {{ if cover }}37 class="object-cover w-full h-full {{ class }}"38 style="object-position: {{ focus | background_position }}"39 {{ else }}40 class="{{ class }}"41 {{ /if }}42 src="{{ glide:image preset='lg' }}"43 alt="{{ alt ensure_right='.' }}"44 width="{{ image.width }}"45 height="{{ image.height }}"46 {{ if lazy }}47 loading="lazy"48 {{ /if }}49 >50 {{ /if }}51 {{ /asset }}52 </picture>53{{ /if }}54<!-- End: /components/_picture.antlers.html -->
The Antlers picture partial as available in Peak.
So what’s exactly happening here?
We’re using a
<picture>
element because it can hold multiple<sources>
for our images. For example if your browser doesn’t support WebP, it will fall back to JPG.On line 11, 37 and 40 you’ll also notice we can define classes for our
<picture>
element. I’ll show an example of how to do this when we call in our partial later.On line 2 we’re using the
{{ asset }}
tag that uses an image as a variable. First on line 10 it checks to see if the asset is an.svg
or.gif
file. In that there's nothing to optimise. Just render the asset with an alt description and be done with it.On line 14 it gets interesting. We’re defining a
srcset
for our imagesource
with all the WebP presets we previously generated using our php configuration file. We’re basically providing the browser a set of images and telling it at whatwidth
to use it.The value of the
sizes
attribute on line 21 and 32 is completely dependent on where and how you’re going to use this image. It expects something like this:(min-width: 768px) 40vw, 90vw
. What we’re telling the browser here is that the image is being rendered at 40vw on screens larger than 768px and 90vw on other (smaller) screens. Together with thesrcset
the browser can now figure out which of the 6 images it should download for the current screen our user is viewing the website on.At line 26 we’re repeating the same stuff for browsers that don’t support WebP and need JPG’s.
On line 33 we define the correct image type.
On line 36 some optional classes and a inline styles can be added when you’re using the
object-fit
css property. This utilizes Statamic’s awesome focal point feature. That way we canobject-fit: cover
our image in a container and maintain focus on a user defined focal point. Here I use Tailwind utility classes.The default image on browsers that don’t support srcsets is the
lg
preset on line 42.And as a bonus: if your browser supports lazy loading that’s defined on line 46.
Calling in the partial
When we’re calling in the partial, just provide it with the necessary arguments. Take this example where I also using aspect ratio utilities to control sizing:
1<figure class="aspect-w-4 aspect-h-3 border border-primary dark:border-secondary">2 {{ partial:components/picture :image="image" cover="true" lazy="true" sizes="(min-width: 768px) 33vw, 90vw" }}3</figure>
Calling in the picture partial. You can do this wherever you need responsive images.
And that’s it. We’re rendering a responsive image thats being held in our image
variable. We’re passing the <picture>
element some utility classes to define its appearance, set cover
to true, and we’re letting the browser figure out which asset to download for 90vw on mobile and 40vw on larger screens. You could also wrap the partial tag in a <figure> element with a <figcaption>. It all depends on your use case.
I hope it’s helpful. Feel free to drop me a line if you have any questions or enhancements.