Andrew Welch · Insights · #craftcms #images #frontend

Published , updated · 5 min read ·


Please consider 🎗 sponsoring me 🎗 to keep writing articles like this.

Creating Optimized Images in Craft CMS

The num­ber one thing you can do to cre­ate a speedy web­site is to opti­mize the images. Here’s how to do it in Craft CMS

Cre­at­ing a fast, opti­mized web­site should not be an after­thought: it should be an inte­gral part of your work­flow, and your pro­pos­als. There are a litany of stud­ies that show how impor­tant web­page load­ing time is for cus­tomer acqui­si­tion, sat­is­fac­tion, con­ver­sion, and reten­tion. It also affects our Search Engine Results Page (SERP) rank­ing in Google, and also let’s not for­get that fast load­ing web­pages make peo­ple using them hap­py. So let’s do right by our clients.

If you’re not going to do any­thing else, the num­ber one thing you can do to cre­ate a per­for­mant web­site is to opti­mize the images, because they are by far the largest part of most web­sites. For­tu­nate­ly, Craft CMS offers a num­ber of tools to help you do this.

The gold­en rule of web­site images is the following:

Never display any client uploaded images on the website. Ever.

That’s right. Nev­er dis­play any­thing your client adds to the web­site, 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 for­mat. They may be a mas­sive 10GB image that their art depart­ment hand­ed them. This can have a huge impact on the user expe­ri­ence when load­ing the web­site, espe­cial­ly for mobile users.

I’ve seen any num­ber of facepalm-wor­thy things in the per­for­mance audits I’ve done. For exam­ple, a mas­sive 2600×1600 image in .png for­mat (it real­ly should have been in .jpg for­mat) that the client uploaded, which the web­site duti­ful­ly dis­played… scaled down to a 400 pix­el-wide plac­ard. I could hear a mil­lion mobile phones sud­den­ly cry out in ter­ror. The list is endless.

In fair­ness, it’s not your clien­t’s job to know these things: it’s yours. So what needs to be done is client-proof­ing” where we trans­form the images that they say they want dis­played into some­thing that is opti­mized for the web, as well as for var­i­ous respon­sive sizes.

We want to dis­play a nice, large image on desk­tops with high speed Inter­net, but dis­play­ing that same image on a tiny mobile phone screen that’s load­ing the web­site over a slow cel­lu­lar con­nec­tion is a trav­es­ty. So let’s have a look at how we can make things better.

If you want an idea of how much more opti­mized your web­site’s images could be, run it through the Cloud­i­nary Image Analy­sis Tool.

This will tell you how large your images are, and how much you could save if they were opti­mized. It’s a real­ly handy tool to get an idea of how much you could actu­al­ly save if you opti­mized the images on your website.

But now that we know how much bet­ter our web­site could be, how do we make it hap­pen? Read on, dear reader!

Link Making the Web a Better Place

Craft CMS comes with built-in image trans­forms that let you do things like scale an image down to a par­tic­u­lar size, or always con­vert images to a par­tic­u­lar for­mat. This works pret­ty well, but there are some plu­g­ins avail­able that allow us to real­ly go the extra mile and cre­ate a great expe­ri­ence. The built-in Craft trans­forms don’t allow you to run them through an image opti­miz­ing tool, and they also don’t allow you to crop them based on what the impor­tant part of the image is.

So let’s look at some tools that let us accom­plish this. The first is a plu­g­in called Imager that lets you do a ton of cool things with images, from resiz­ing them to apply­ing fil­ters, and a whole lot more. Cru­cial­ly for us, Imager also allows us to run the result­ing images through image opti­miza­tion tools after they’ve been created.

But first, we have to do a tiny bit of work. After you’ve installed the Imager plu­g­in, ssh into your serv­er and type the fol­low­ing to install the jpegoptim & optipng tools, depend­ing on your fla­vor 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 plu­g­in fold­er, dupli­cate the config.php file, rename it imager.php, and copy that imager.php file into your craft/config direc­to­ry. This file lets us cus­tomize how Imager works. Open the imager.php file up in your text edi­tor, and enable jpegoptim & optipng like this:

  'jpegoptimEnabled' => true,
  'optipngEnabled' => true,

That’s it, you’re done! Imager will now auto­mat­i­cal­ly opti­mize the var­i­ous image trans­forms that it cre­ates to ensure that they are great for dis­play­ing on the web. Imager has a whole lot more it can do, and many oth­er con­fig­u­ra­tion options, so check out the Imager doc­u­men­ta­tion for details.

By installing these tools on the serv­er, we’ve made it pos­si­ble to opti­mize the images that the client uploads with­out any inter­ven­tion from us, and with­out them hav­ing to do a thing. It’s almost like magic.

Keep in mind that the image opti­miza­tion that jpegoptim & optipng do is loss­less. They just remove the cruft we don’t need when dis­play­ing images on the web.

If you real­ly want to go the extra mile, check out the Installing mozjpeg on Ubun­tu 16.04 (Forge) article.

Link Creating Optimized Responsive Images

So this is great, we have serv­er-side image opti­miza­tion cour­tesy of Imager and jpegoptim/optipng. But how do we then take advan­tage of them in our fron­tend tem­plates to make beau­ti­ful, opti­mized respon­sive images?

The first thing we’re going to do is install anoth­er plu­g­in called Focus­Point. Clients love this plu­g­in, because it gives them con­trol over their content.

Focus­Point let­ting us pick the wom­an’s face as the impor­tant part of the image

Focus­Point allows the client to choose the part of the image that is most impor­tant to them, so that when the var­i­ous respon­sive images are cre­at­ed in a vari­ety of aspect ratios, it’ll keep the image focused on that point. Thus, Focus­Point. This helps to pre­vent gaffes like heads being decap­i­tat­ed or non-impor­tant parts of the image dis­play­ing at a vari­ety of sizes.

All you have to do is change your Assets fields to Focus­Point fields, and they work just as you’re used to, but you’ll see an addi­tion­al gear icon you can click on to pick the focus point for the image. You can even change exist­ing Assets fields into Focus­Point fields with­out any data loss. OK, so now that we have Imager & Focus­Point all set up, let’s cre­ate some beau­ti­ful images!

Since we want to serve images opti­mized for the size of the device that’s view­ing our web­page, we’re going to use the srcset attribute. It’s wide­ly sup­port­ed in mod­ern browsers, and there is a poly­fill we can use to sup­port old­er browsers. Imager has some great tools for mak­ing 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 actu­al code from the blog you’re read­ing right now, for the large image that appears at the top of the blog! In a nut­shell, we tell Imager what sizes we want our images to be, and it cre­ates them with the set­tings we’ve asked it to. Then the craft.imager.srcset func­tion spits out all of the respon­sive images for us. A full expla­na­tion of srcset is out­side of the scope of this blog, but check out this tuto­r­i­al on src­set for a primer.

Note that in addi­tion to cre­at­ing images in var­i­ous sizes, we’ve also spec­i­fied a dif­fer­ent aspect ratio & JPEG com­pres­sion for our mobile” sized images, and we’re forc­ing the for­mat of the images to be .jpg. Imager has a ton of options, so again, see the Imager doc­u­men­ta­tion for details on what else you can do.

Two things that bear men­tion­ing 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 cre­ate pro­gres­sive JPEGs for us.

The result­ing HTML code that it spits out looks like this (I just for­mat­ted it a bit nicer, to make it eas­i­er 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">

Sor­ry for the long, ugly URLs, that’s just my Con­tent Deliv­ery Net­work (CDN) at work. That’s it! We’ve got opti­mized images, sized appro­pri­ate­ly for our CSS’s @media break­points. 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 impor­tant, and the web­site will do the right thing.

To real­ly go the extra mile, you can use the Preparse field in your entries. Put the tem­plate code into your Preparse field that gen­er­ates the Imager trans­forms there… so that the image trans­forms are cre­at­ed at the time the entry is saved, rather than on page load. It works great in com­bi­na­tion with Imager.

The client will nev­er see or inter­act with the Preparse field; it’s just a way to get Imager to cre­ate the trans­forms at a non-crit­i­cal time (when an entry is saved in the back­end), as opposed to a crit­i­cal time (when a user loads the fron­tend web­page). All image trans­forms should be gen­er­at­ed on the back­end, and nev­er on the frontend. 

Anoth­er way to accom­plish the same thing is to use the Imager Pre­trans­form plugin.

Link Let’s Get Lazy!

There is one more thing we’ll want to do in order to make this a nice, com­plete pack­age. And the cool thing about it is that it involves being lazy! Stud­ies have shown that per­ceived per­for­mance is all about dis­play­ing some­thing to the user ASAP. So we don’t want to have to load every sin­gle image on the page when the web­site is loaded.

Instead, we load any images that are above the fold (that is, in the vis­i­ble part of the brows­er when the web­page is loaded) right away, and all of the oth­er images we lazy load. That means that the images don’t load until they are actu­al­ly revealed, whether that’s via scrolling down, or pop­ping up a modal win­dow or what have you.

This can be espe­cial­ly impor­tant 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 real­ly need­ed yet, we can slow the per­ceived per­for­mance of our web­site down. So instead, we load them as-needed.

To do this, we’ll use a fan­tas­tic JavaScript called lazy­sizes. It’s super-sim­ple to use, just include the JavaScript on your web­page, 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 attrib­ut­es, and lazysizes does the rest! As soon as the image is vis­i­ble, it’ll be loaded and dis­played. Note that we also pass in as the src an inline grey pix­el as a place­hold­er, gen­er­at­ed by craft.imager.base64Pixel(). There is even an exten­sion avail­able for lazysizes called bgset that gives you the same lazy­load­ing of srcset images for CSS background-image prop­er­ties. You can use Imager to gen­er­ate them, too.

This blog actu­al­ly uses this lazy load­ing tech­nique: below the fold images are all lazy loaded. Pret­ty cool, eh?

Link Adding .webp Support

I’d also high­ly sug­gest read­ing the arti­cle Using WebP images in Craft with Imager to add webp sup­port via Imager.

You use the exact tech­niques pre­sent­ed here, but you just wrap your <img> tags in a <picture> ele­ment. I imple­ment­ed 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 every­thing we dis­cussed in this arti­cle, but we’re sim­ply adding webp images to the mix, if the serv­er sup­ports gen­er­at­ing webp images.

Then the brows­er gets to choose what image to use, if it sup­ports webp, it’ll use that. If not, it’ll fall back on our jpg images. Boom.

jpg vs. webp, 37k vs 16k for the same image!

So how much can you save? The same image, at the same res­o­lu­tion, at the same com­pres­sion %, from the same source image is 37k in .jpg for­mat, and 16k in .webp format.

Wow.

Now go forth and make some fan­tas­tic opti­mized images for your clients! For real-world results, check out the Post-Mortem: Applied Image Opti­miza­tion article.