Efficient Responsive Images in WordPress

Development,Projects,Snippets,WordPress,WordPress Filters

Most of the civilized world has heard by now that WordPress Core includes native support for responsive images (since 4.4). The concept of responsive images has been around for a while, so I’ll assume you know all about srcset and sizes. I knew about the power of responsive images and how to use them, but I struggled to integrate them into my theme development workflow effectively, so I want to demonstrate a solution I devised. Most other posts on the topic either discuss how srcset and sizes work, or just touch on the functions and filters you can use in WordPress, but they don’t demonstrate efficient workflows.

Skip to the final snippet

How Responsive Images in WordPress Work vs. How They Should Work

WordPress’s responsive image powers come from automatically generating srcset and sizes attributes for img tags. It does this automatically, not necessarily intelligently. Perhaps you’ve wrestled with how difficult and complex it is to customize those tags efficiently (I sure did). The Core team actually said, very concisely:

To help browsers select the best image from the source set list, we also include a default sizes attribute that is equivalent to (max-width: {{image-width}}px) 100vw, {{image-width}}px. While this default will work out of the box for a majority of sites, themes should customize the default sizes attribute as needed using the wp_calculate_image_sizes filter.

So there you have it. Just customize the default sizes attribute as needed using the wp_calculate_image_sizes filter. Done!

Well, not quite. I struggled because there’s actually only one default for the sizes attribute, but my themes almost always require multiple image sizes and variations. Some images are full width and others are just portraits or small CTAs/banners. You can certainly filter the responsive attributes by image ID or by filename, but the problem is that those values can both change.

It would be great to assign a name to any image based on where it appears in a template, e.g. “header_image,” so that we’re not constrained by the actual image’s metadata, just our chosen name for its position in our theme. Then we can tweak its responsive attributes in our functions.php (or other function) file. I didn’t see a way to do that in an efficient, flexible, sustainable way, so I made a few functions that enable that workflow.

Let’s Start at the End: We Need a Custom Filter

What do we really need to make this efficient? We really need to filter the default sizes attributes on a per image basis, based on a constant that won’t be affected by image ID or filename changing. I chose to do this by assigning an arbitrary name to each image and then creating the sizes string based on that arbitrary name (therefore making it slightly less arbitrary).

One important note to keep in mind is that WordPress automatically creates the srcset and sizes attributes only for images that use its native functions, such as wp_get_attachment_image() (it also automatically filters images inserted into your post content to add the attributes, too). Therefore, you must use the native WordPress functions to output your images in your templates for this to work, even if you use a custom fields plugin. A couple of quick examples of what works and what doesn’t:

<?php
/**
* EXAMPLE 1
*
* This will *NOT* work because WordPress will not genereate responsive
* image attributes. This example assumes you're using Advanced Custom
* Fields for your image.
*/
$image = get_field('my_image');
?>
<div class="example">
<img src="<?php echo $image['url']; ?>">
</div>
<?php
/**
* EXAMPLE 2
*
* This will work, but doesn't allow much control over the responsive
* image attributes.
*/
$image = get_field('my_image');
?>
<div class="example">
<?php echo wp_get_attachment_image($image['ID'], 'original'); ?>
</div>
view raw intro_examples.php hosted with ❤ by GitHub

Ok, hopefully you’re using the correct method to output your images, and with that out of the way we can now setup our filter for the sizes attribute.

Filter Setup

I didn’t want to reinvent the wheel with my solution, so I took a look at what happens when you call wp_get_attachment_image(). I noticed that it checks the attributes argument for srcset and sizes and if they’re empty then it generates its defaults, which is precisely the step that we want more control over. We could just add our own attributes each time we call that function, but it means your custom attribute values will be scattered throughout your templates making for tedious editing, and your templates could end up looking very cluttered. Instead of doing that, I actually filter these attributes in the function call:

This enables me to filter the responsive image arguments all in one place (my functions.php file), but more importantly, it passes an arbitrary, custom image name for use in a filter function:

<?php
/*
* THE FILTER
*
*/
function custom_responsive_image_sizes($sizes, $img_name, $attachment_id) {
$sizes = wp_get_attachment_image_sizes($attachment_id, 'original');
$meta = wp_get_attachment_metadata($attachment_id);
$width = $meta['width'];
if(!$width) {
return $sizes;
}
if('long_example_image' == $img_name) {
$sizes = '(max-width: 1337px) 1337px, 100vw';
}
return $sizes;
}
add_filter('responsive_image_sizes', 'custom_responsive_image_sizes', 10, 3);
view raw filter.php hosted with ❤ by GitHub

Making the Filter Easier to Use

You could get away with just that filter, but it’s not especially efficient. We either need to remember that function call and our custom filters which we pass as arguments, or copy/paste that block each time we call an image in our template. I ended up creating a custom function that we can just throw right into our templates.

<?php
/**
* Get an HTML img element representing an image attachment while allowing
* custom image names and individual size/srcset filtering
*
* @param int $attachment_id Image attachment ID.
* @param string $name Optional. Custom image name used for filtering purposes. Default
* 'default'.
* @param string|array $size Optional. Image size. Accepts any valid image size, or an array of width
* and height values in pixels (in that order). Default 'original'.
* @param bool $icon Optional. Whether the image should be treated as an icon. Default false.
* @param string|array $attr Optional. Attributes for the image markup. Default are filtered responsive
* attributes.
* @return string HTML img element or empty string on failure.
*/
function get_responsive_attachment_image($attachment_id, $name = 'default', $size = 'original', $icon = false, $attr = '') {
if(!$attachment_id) {
return false;
}
$src = wp_get_attachment_image_src($attachment_id, $size, $icon);
if(!$src) {
return false;
}
$default_attr = array(
'srcset' => apply_filters('responsive_image_srcset', false, $name, $attachment_id),
'sizes' => apply_filters('responsive_image_sizes', false, $name, $attachment_id)
);
$attr = wp_parse_args($attr, $default_attr);
$img = wp_get_attachment_image($attachment_id, $size, false, $attr);
return $img;
}

After you place that function inside your functions.php file then you’re free to call it anywhere in your theme. Then you can simply call get_responsive_attachment_image() with your image ID and an optional name for it and you’ll be able to filter each sizes and srcset attribute string individually, based on the name you pass. Since each call includes the filters and the image name, that means you can keep all of your sizes and srcset modifications in your functions.php file too, making them easier to edit because they’re all in one place.

The Final Code

Our final code to place inside functions.php is:

After that’s in place, you can use the function in your template files like this:

You can pass any image name and then edit its sizes string over in the filter function, or you can pass no image name and setup a default sizes string of your own inside the filter function. You can do the same for srcset with the final functions and filters, but my example revolves around sizes because I found they needed the most attention. In fact, implementing this alone managed to drop the total size of a client’s site by about 50% on mobile.

I hope this helps you wrap your mind around a good responsive image workflow in WordPress.

Questions? Comments? Suggestions? Send me a comment below!


Leave a Comment