Andrew Welch
Published , updated · 5 min read · RSS Feed
Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.
Creating Optimized Images in Craft CMS
The number one thing you can do to create a speedy website is to optimize the images. Here’s how to do it in Craft CMS
Creating a fast, optimized website should not be an afterthought: it should be an integral part of your workflow, and your proposals. There are a litany of studies that show how important webpage loading time is for customer acquisition, satisfaction, conversion, and retention. It also affects our Search Engine Results Page (SERP) ranking in Google, and also let’s not forget that fast loading webpages make people using them happy. So let’s do right by our clients.
If you’re not going to do anything else, the number one thing you can do to create a performant website is to optimize the images, because they are by far the largest part of most websites. Fortunately, Craft CMS offers a number of tools to help you do this.
The golden rule of website images is the following:
Never display any client uploaded images on the website. Ever.
That’s right. Never display anything your client adds to the website, because clients will, by and large, not be aware of the impact of the images that they choose to use. They may be in the wrong format. They may be a massive 10GB image that their art department handed them. This can have a huge impact on the user experience when loading the website, especially for mobile users.
I’ve seen any number of facepalm-worthy things in the performance audits I’ve done. For example, a massive 2600×1600 image in .png format (it really should have been in .jpg format) that the client uploaded, which the website dutifully displayed… scaled down to a 400 pixel-wide placard. I could hear a million mobile phones suddenly cry out in terror. The list is endless.
In fairness, it’s not your client’s job to know these things: it’s yours. So what needs to be done is “client-proofing” where we transform the images that they say they want displayed into something that is optimized for the web, as well as for various responsive sizes.
We want to display a nice, large image on desktops with high speed Internet, but displaying that same image on a tiny mobile phone screen that’s loading the website over a slow cellular connection is a travesty. So let’s have a look at how we can make things better.
If you want an idea of how much more optimized your website’s images could be, run it through the Cloudinary Image Analysis Tool.
This will tell you how large your images are, and how much you could save if they were optimized. It’s a really handy tool to get an idea of how much you could actually save if you optimized the images on your website.
But now that we know how much better our website could be, how do we make it happen? Read on, dear reader!
Link Making the Web a Better Place
Craft CMS comes with built-in image transforms that let you do things like scale an image down to a particular size, or always convert images to a particular format. This works pretty well, but there are some plugins available that allow us to really go the extra mile and create a great experience. The built-in Craft transforms don’t allow you to run them through an image optimizing tool, and they also don’t allow you to crop them based on what the important part of the image is.
So let’s look at some tools that let us accomplish this. The first is a plugin called Imager that lets you do a ton of cool things with images, from resizing them to applying filters, and a whole lot more. Crucially for us, Imager also allows us to run the resulting images through image optimization tools after they’ve been created.
But first, we have to do a tiny bit of work. After you’ve installed the Imager plugin, ssh into your server and type the following to install the jpegoptim & optipng tools, depending on your flavor of Linux:
sudo apt-get install jpegoptim
sudo apt-get install optipng
-OR-
sudo yum install jpegoptim
sudo yum install optipng
Then go into the imager plugin folder, duplicate the config.php file, rename it imager.php, and copy that imager.php file into your craft/config directory. This file lets us customize how Imager works. Open the imager.php file up in your text editor, and enable jpegoptim & optipng like this:
'jpegoptimEnabled' => true,
'optipngEnabled' => true,
That’s it, you’re done! Imager will now automatically optimize the various image transforms that it creates to ensure that they are great for displaying on the web. Imager has a whole lot more it can do, and many other configuration options, so check out the Imager documentation for details.
By installing these tools on the server, we’ve made it possible to optimize the images that the client uploads without any intervention from us, and without them having to do a thing. It’s almost like magic.
Keep in mind that the image optimization that jpegoptim & optipng do is lossless. They just remove the cruft we don’t need when displaying images on the web.
If you really want to go the extra mile, check out the Installing mozjpeg on Ubuntu 16.04 (Forge) article.
Link Creating Optimized Responsive Images
So this is great, we have server-side image optimization courtesy of Imager and jpegoptim/optipng. But how do we then take advantage of them in our frontend templates to make beautiful, optimized responsive images?
The first thing we’re going to do is install another plugin called FocusPoint. Clients love this plugin, because it gives them control over their content.
FocusPoint allows the client to choose the part of the image that is most important to them, so that when the various responsive images are created in a variety of aspect ratios, it’ll keep the image focused on that point. Thus, FocusPoint. This helps to prevent gaffes like heads being decapitated or non-important parts of the image displaying at a variety of sizes.
All you have to do is change your Assets fields to FocusPoint fields, and they work just as you’re used to, but you’ll see an additional gear icon you can click on to pick the focus point for the image. You can even change existing Assets fields into FocusPoint fields without any data loss. OK, so now that we have Imager & FocusPoint all set up, let’s create some beautiful images!
Since we want to serve images optimized for the size of the device that’s viewing our webpage, we’re going to use the srcset attribute. It’s widely supported in modern browsers, and there is a polyfill we can use to support older browsers. Imager has some great tools for making this easy, have a look:
{% set image = block.image.first() %}
{% set transformedImages = craft.imager.transformImage(image,
[
{ width: 1200, ratio: 2/1 },
{ width: 1024, ratio: 2/1 },
{ width: 768, ratio: 4/3, jpegQuality: 65 },
],
{
format: 'jpg',
allowUpscale: false,
mode: 'crop',
jpegQuality: 80,
position: image.focusPctX ~ '% ' ~ image.focusPctY ~ '%',
interlace: true
}
) %}
<img class="scale-with-grid"
src="{{ transformedImages[1].url }}"
sizes="100vw"
srcset="{{ craft.imager.srcset(transformedImages) }}"
alt="{{ image.title }}">
This is actual code from the blog you’re reading right now, for the large image that appears at the top of the blog! In a nutshell, we tell Imager what sizes we want our images to be, and it creates them with the settings we’ve asked it to. Then the craft.imager.srcset function spits out all of the responsive images for us. A full explanation of srcset is outside of the scope of this blog, but check out this tutorial on srcset for a primer.
Note that in addition to creating images in various sizes, we’ve also specified a different aspect ratio & JPEG compression for our “mobile” sized images, and we’re forcing the format of the images to be .jpg. Imager has a ton of options, so again, see the Imager documentation for details on what else you can do.
Two things that bear mentioning are the position: image.focusPctX ~ '% ' ~ image.focusPctY ~ '%' which grabs the focus point we chose for the image, and tells Imager to crop the image around that point, and the interlace: true which tells Imager to create progressive JPEGs for us.
The resulting HTML code that it spits out looks like this (I just formatted it a bit nicer, to make it easier to read):
<img class="scale-with-grid"
src="https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/imager/img/blog/139/image_optimzation_cd9b1b12709096438df3a7647ef1558f.jpg"
sizes="100vw"
srcset="https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/imager/img/blog/139/image_optimzation_3cd01b32cf075295339272842e63a5e6.jpg 1200w,
https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/imager/img/blog/139/image_optimzation_cd9b1b12709096438df3a7647ef1558f.jpg 1024w,
https://nystudio107-ems2qegf7x6qiqq.netdna-ssl.com/imager/img/blog/139/image_optimzation_21abce98148c7302c18c665595eb1425.jpg 768w"
alt="Image Optimization">
Sorry for the long, ugly URLs, that’s just my Content Delivery Network (CDN) at work. That’s it! We’ve got optimized images, sized appropriately for our CSS’s @media breakpoints. And the best part is, the client can upload the images in any size/format they want, as well as pick the part of the image that they think is most important, and the website will do the right thing.
To really go the extra mile, you can use the Preparse field in your entries. Put the template code into your Preparse field that generates the Imager transforms there… so that the image transforms are created at the time the entry is saved, rather than on page load. It works great in combination with Imager.
The client will never see or interact with the Preparse field; it’s just a way to get Imager to create the transforms at a non-critical time (when an entry is saved in the backend), as opposed to a critical time (when a user loads the frontend webpage). All image transforms should be generated on the backend, and never on the frontend.
Another way to accomplish the same thing is to use the Imager Pretransform plugin.
Link Let’s Get Lazy!
There is one more thing we’ll want to do in order to make this a nice, complete package. And the cool thing about it is that it involves being lazy! Studies have shown that perceived performance is all about displaying something to the user ASAP. So we don’t want to have to load every single image on the page when the website is loaded.
Instead, we load any images that are above the fold (that is, in the visible part of the browser when the webpage is loaded) right away, and all of the other images we lazy load. That means that the images don’t load until they are actually revealed, whether that’s via scrolling down, or popping up a modal window or what have you.
This can be especially important if you have a page that has a ton of images, and also if you’re using http2 (and if you’re not, you should be!), because all of the requests share one stream. If we try to load a ton of images that aren’t really needed yet, we can slow the perceived performance of our website down. So instead, we load them as-needed.
To do this, we’ll use a fantastic JavaScript called lazysizes. It’s super-simple to use, just include the JavaScript on your webpage, and then change this:
<img class="scale-with-grid"
src="{{ transformedImages[1].url }}"
sizes="100vw"
srcset="{{ craft.imager.srcset(transformedImages) }}"
alt="{{ image.title }}">
To this:
<img class="scale-with-grid lazyload"
src="{{ craft.imager.base64Pixel(2,1) }}"
data-sizes="100vw"
data-srcset="{{ craft.imager.srcset(transformedImages) }}"
alt="{{ image.title }}">
All we did was add the class lazyload to our <img>, and prepend data- to the sizes and srcset attributes, and lazysizes does the rest! As soon as the image is visible, it’ll be loaded and displayed. Note that we also pass in as the src an inline grey pixel as a placeholder, generated by craft.imager.base64Pixel(). There is even an extension available for lazysizes called bgset that gives you the same lazyloading of srcset images for CSS background-image properties. You can use Imager to generate them, too.
This blog actually uses this lazy loading technique: below the fold images are all lazy loaded. Pretty cool, eh?
Link Adding .webp Support
I’d also highly suggest reading the article Using WebP images in Craft with Imager to add webp support via Imager.
You use the exact techniques presented here, but you just wrap your <img> tags in a <picture> element. I implemented it on this very site, here’s what I did:
{% if craft.imager.serverSupportsWebp() %}
{% set transformedImagesWebP = craft.imager.transformImage(image,[
{ width: 1200, ratio: 2/1, webpQuality: 75 },
{ width: 1024, ratio: 2/1, webpQuality: 75 },
{ width: 768, ratio: 4/3, webpQuality: 60 },
],{ format: 'webp', allowUpscale: false, mode: 'crop', position: image.focusPctX ~ '% ' ~ image.focusPctY ~ '%', interlace: true }) %}
{% endif %}
<picture>
{% if lazyImage %}
{% if craft.imager.serverSupportsWebp() %}
<source data-sizes="100vw" data-srcset="{{ craft.imager.srcset(transformedImagesWebP) }}" type="image/webp">
{% endif %}
<img class="scale-with-grid lazyload" src="{{ craft.imager.base64Pixel(2,1) }}" data-sizes="100vw" data-srcset="{{ craft.imager.srcset(transformedImages) }}" alt="{{ image.title }}" height="auto" width="100%">
{% else %}
{% if craft.imager.serverSupportsWebp() %}
<source sizes="100vw" srcset="{{ craft.imager.srcset(transformedImagesWebP) }}" type="image/webp">
{% endif %}
<img class="scale-with-grid" src="{{ transformedImages[1].url }}" sizes="100vw" srcset="{{ craft.imager.srcset(transformedImages) }}" alt="{{ image.title }}" height="auto" width="100%">
{% endif %}
</picture>
We’re doing everything we discussed in this article, but we’re simply adding webp images to the mix, if the server supports generating webp images.
Then the browser gets to choose what image to use, if it supports webp, it’ll use that. If not, it’ll fall back on our jpg images. Boom.
So how much can you save? The same image, at the same resolution, at the same compression %, from the same source image is 37k in .jpg format, and 16k in .webp format.
Wow.
Now go forth and make some fantastic optimized images for your clients! For real-world results, check out the Post-Mortem: Applied Image Optimization article.