Using Responsive Images with Craft

Michael Westwood
4 min readApr 4, 2016

Note: Since writing this article, I’ve written a better one on the same subject. This one.

We at Dust have been using Craft as our Content Management System of choice now for a while, with incredibly positive feedback from the rest of the team as well as clients.

We’ve been putting together a boilerplate for new Craft projects that has all the bits and bobs you need to get started. The project is currently on Github.

This is a guide showing the latest addition to the boilerplate dealing with responsive images managed through the CMS.

I’m assuming that you know the whats and whys of responsive images, and you know what the <picture> element is. If not, there’s a great Smashing article on the subject. But basically, you want to serve small images to small screens, and large images to large screens, and the <picture> element allows you to specify which images are loaded when.

First thing’s first.

Let’s just clarify what we’re trying to do. We’re trying to upload a single image to the CMS, and serve a differently sized version of the same image at each defined breakpoint.

So we’ll upload image.jpg, and at a viewport 1440px wide, we’ll show image-xl.jpg. At 1040px, image-l.jpg; 800px, image-m.jpg; 600px, image-s.jpg; and at 480px, we’ll show image-xs.jpg. If you’re wondering what’s with the animals, see this CSS Tricks article on naming media queries.

Currently I’m not worrying about art directed images, just straight up unintelligent crops. I know Cloudinary are doing some cool stuff regarding that, but that’s a whole different topic.

Let’s make a partial.

We want to churn out a picture element with all the specified breakpoints and resized images. The most sensible way to do this in Craft/Twig is to create a template partial, where you pass the image in as a parameter.

You’ll want to specify a few different transforms for different parts of your templates. So you’ll have, for example, a headerLogo transform, an articleTeaser transform, a pageHero transform, etc. So when we call the picture partial, we’ll need to specify a transform.

Here’s some code:

{% set bearSrc = transform ~ ‘Bear’ %}
{% set wolfSrc = transform ~ ‘Wolf’ %}
{% set foxSrc = transform ~ ‘Fox’ %}
{% set rabbitSrc = transform ~ ‘Rabbit’ %}
{% set mouseSrc = transform ~ ‘Mouse’ %}
<picture>
<!--[if IE 9]><video style=”display: none;”><![endif]-->
<source srcset=”{{ image.getUrl(bearSrc) }}” media=”(min-width: 1041px)”>
<source srcset=”{{ image.getUrl(wolfSrc) }}” media=”(min-width: 801px)”>
<source srcset=”{{ image.getUrl(foxSrc) }}” media=”(min-width: 601px)”>
<source srcset=”{{ image.getUrl(rabbitSrc) }}” media=”(min-width: 481px)”>
<source srcset=”{{ image.getUrl(mouseSrc) }}”>
<!--[if IE 9]></video><![endif]-->
<img srcset=”image-mouse.jpg” alt=”{{ image.title }}”>
</picture>

That’s basically all you need. That’ll give you a picture element with all the transforms from the database.

Now, you might have realised that this means creating five transforms in the database as well (headerLogoBear, headerLogoWolf, headerLogoFox, headerLogoRabbit, headerLogoMouse). That’s a lot of effort, but totally doable. But a lot of effort.

Reducing effort.

If you’re in to the idea of not managing transforms from the admin, but instead managing them from your templates, then this is for you. Here’s some more code:

<picture>
<!--[if IE 9]><video style=”display: none;”><![endif]-->
<source srcset=”{{ transforms.getTransform(image, transform, ‘bear’) }}” media=”(min-width: 1041px)”>
<source srcset=”{{ transforms.getTransform(image, transform, ‘wolf’) }}” media=”(min-width: 801px)”>
<source srcset=”{{ transforms.getTransform(image, transform, ‘fox’) }}” media=”(min-width: 601px)”>
<source srcset=”{{ transforms.getTransform(image, transform, ‘rabbit’) }}” media=”(min-width: 481px)”>
<source srcset=”{{ transforms.getTransform(image, transform, ‘mouse’) }}”>
<!--[if IE 9]></video><![endif]-->
<img srcset=”image-mouse.jpg” alt=”{{ image.title }}”>
</picture>

The difference here is not defining different bearSrc, wolfSrc, etc. Instead we’re calling a macro called getTransform().

Inside getTransform is a massive object of all your transforms — widths, heights, crops, formats, etc.

getTransform() does some parsing and some object merging and gives you your transform object, which is passed into image.getUrl() and you’ll get back something like _220xAUTO_crop_center-center_100/image.png.

Boom. Responsive images using a CMS, without the need to use the admin area.

Performance.

For extra performance points, I’d recommend using Alexander Farkas’s lazysizes script to defer the loading of your images until the user has scrolled past them. You might not want to lazyload all your images, so I’ve added this as a parameter for the partial.

Things for the future.

Due to macro scope, it has to parse the whole transforms object every time you want a transform, adding more server response time :(

It feels like all the configuration here would be best served as a JSON file that’s read in, though I’ve not yet looked into how to do this.

I’ve also yet to adapt the partial to do anything with 2x or 3x images. I probably should.

Any thoughts, criticisms, suggestions, etc are welcome.

--

--