Controlling Responsive Images in WordPress: Breaking Down the Code
Morten Rand-Hendriksen
Tech Educator | Keynote Speaker | Pragmatic Futurist | Critical Writer | Neurodivergent System Thinker | Dad
When you add an image to a WordPress post or page, whether as a featured image or one in the content, the CMS outputs markup for responsive images. This means WordPress generates a series of different sized versions of the image, and the actual image file served up will be the one most suited for the browser width and screen resolution of the visitor's device. Responsive images is a significant leap forward in web technologies, and the automation provided by WordPress makes this rather complex standard as easy as just uploading an image and letting the application do the rest.
The Problem
For the browser to be able to pick the most appropriate image size, it needs to know how big the area the image is being displayed in really is. And this size is defined by the current active theme, not WordPress itself. As a result, unless the theme clearly states how big the display area for an image is, WordPress will safe it by setting the default width of the image to the width of the image file itself. This in turn means if you upload a large image, say 2600px wide, WordPress will say the maximum displayed width of the image is 2600px. This is rarely true, and as a result the good intentions of responsive images are being somewhat squandered, particularly on wider displays.
The Solution
The sizes attribute was created specifically to deal with this issue. Using sizes in conjunction with srcset allows us to use simple media queries to tell the browser how wide the display area for an image is before the actual page layout has been painted, and the browser can pick the right size image file to download. The challenge is to get WordPress to provide the correct sizes attribute for the current theme.
The good news is we can build custom sizes attributes for both featured images and regular displayed content into our themes, and make them conditional on whatever crazy layout configurations we can think of.
The good people who developed the Twenty Sixteen theme put together just such a solution, and I had the privilege of picking it apart and reworking it for the upcoming Twenty Seventeen theme.
While it is fresh in my mind, I want to break down the original code for you so you can see how it works and how you can implement the same feature in your own themes (and plugins).
The Code
Here's what you'll find in Twenty Sixteen's functions.php file. There are two functions at play. First the function for general images displayed in posts and pages, aka "content images":
/**
* Add custom image sizes attribute to enhance responsive image functionality
* for content images
*
* @since Twenty Sixteen 1.0
*
* @param string $sizes A source size value for use in a 'sizes' attribute.
* @param array $size Image size. Accepts an array of width and height
* values in pixels (in that order).
* @return string A source size value for use in a content image 'sizes' attribute.
*/
function twentysixteen_content_image_sizes_attr( $sizes, $size ) {
$width = $size[0];
840 <= $width && $sizes = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px';
if ( 'page' === get_post_type() ) {
840 > $width && $sizes = '(max-width: ' . $width . 'px) 85vw, ' . $width . 'px';
} else {
840 > $width && 600 <= $width && $sizes = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px';
600 > $width && $sizes = '(max-width: ' . $width . 'px) 85vw, ' . $width . 'px';
}
return$sizes;
}
add_filter( 'wp_calculate_image_sizes', 'twentysixteen_content_image_sizes_attr', 10 , 2 );
Second, a function targeting only featured images:
/**
* Add custom image sizes attribute to enhance responsive image functionality
* for post thumbnails
*
* @since Twenty Sixteen 1.0
*
* @param array $attr Attributes for the image markup.
* @param int $attachment Image attachment ID.
* @param array $size Registered image size or flat array of height and width dimensions.
* @return string A source size value for use in a post thumbnail 'sizes' attribute.
*/
function twentysixteen_post_thumbnail_sizes_attr( $attr, $attachment, $size ) {
if ( 'post-thumbnail' === $size ) {
is_active_sidebar( 'sidebar-1' ) && $attr['sizes'] = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 60vw, (max-width: 1362px) 62vw, 840px';
! is_active_sidebar( 'sidebar-1' ) && $attr['sizes'] = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 88vw, 1200px';
}
return$attr;
}
add_filter( 'wp_get_attachment_image_attributes', 'twentysixteen_post_thumbnail_sizes_attr', 10 , 3 );
Featured Images
Let's look at the second one first since it's more straight forward.
We start off with a function that grabs a bunch of attributes ($attr, $attachment, $size) from the Featured Image. This function runs as a callback to the wp_get_attachment_image_attributes hook which we use to filter the output of the featured image markup. So in plain english, we get all the attributes for the Featured Image of the current post or page, then use a custom function to alter these attributes before returning them to WordPress for display on the front end:
functiontwentysixteen_post_thumbnail_sizes_attr( $attr, $attachment, $size ){
// do stuff to the attributes
}
add_filter( 'wp_get_attachment_image_attributes', 'twentysixteen_post_thumbnail_sizes_attr', 10 , 3 );
Inside the function, a conditional statement is used to target only a specific image size, in this case the post-thumbnail size (I tested for different conditions in Twenty Seventeen):
if ( 'post-thumbnail' === $size ) {
// happens only if the condition is met
}
And finally we alter the sizes attribute based on the current layout (I've broken this down a bit to make it easier to read):
is_active_sidebar( 'sidebar-1' ) && $attr['sizes'] = '
?(max-width: 709px) 85vw,
?(max-width: 909px) 67vw,
?(max-width: 984px) 60vw,
?(max-width: 1362px) 62vw,
?840px';
! is_active_sidebar( 'sidebar-1' ) && $attr['sizes'] = '
? (max-width: 709px) 85vw,
?(max-width: 909px) 67vw,
?(max-width: 1362px) 88vw,
?1200px';
There are two conditions here, the first if a sidebar is present, the second if there is no sidebar. In each case the sizes attribute is updated to fit the specified list which reads something like this (for the first condition):
- If the viewport width is 709px or lower, the width of the area the image will be displayed in will be 85% of the total viewport width.
- If the viewport width is over 709px but at or lower than 909px, the width of the area the image will be displayed in will be 67% of the total viewport width.
- Etc
The last property, which only lists a width, is the fallback that stipulates the maximum width the image will ever be displayed as regardless of the width of the viewport.
So, if the current post has a sidebar and the viewport width is 2000px, the browser will still only download an image that is either 1x, 2x, or 3x of the displayed width of 840px.
To wrap things up, the $attr attribute is returned to the callback and applied to the output of the featured image:
return$attr;
Pretty cool.
Content Images
The content image function is a bit more complex, but contains many of the same elements. To refresh your memory it looks like this:
/**
* Add custom image sizes attribute to enhance responsive image functionality
* for content images
*
* @since Twenty Sixteen 1.0
*
* @param string $sizes A source size value for use in a 'sizes' attribute.
* @param array $size Image size. Accepts an array of width and height
* values in pixels (in that order).
* @return string A source size value for use in a content image 'sizes' attribute.
*/
function twentysixteen_content_image_sizes_attr( $sizes, $size ) {
$width = $size[0];
840 <= $width && $sizes = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px';
if ( 'page' === get_post_type() ) {
840 > $width && $sizes = '(max-width: ' . $width . 'px) 85vw, ' . $width . 'px';
} else {
840 > $width && 600 <= $width && $sizes = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px';
600 > $width && $sizes = '(max-width: ' . $width . 'px) 85vw, ' . $width . 'px';
}
return$sizes;
}
add_filter( 'wp_calculate_image_sizes', 'twentysixteen_content_image_sizes_attr', 10 , 2 );
Like before, we start with getting a hook, this time wp_calculate_image_sizes which calculates the sizes attribute for images, and filtering its output with a custom function:
functiontwentysixteen_content_image_sizes_attr( $sizes, $size ){
// do stuff to the attributes
}
add_filter( 'wp_calculate_image_sizes', 'twentysixteen_content_image_sizes_attr', 10 , 2 );
Inside the function, a bunch of things happen:
First, we populate grab the $size property which holds an array containing the width and height of the image, and set the $width to the first array item (the image width):
$width = $size[0];
Next we use a bit of a convoluted PHP logic chain to check for a condition and set the $sizes attribute accordingly:
840 <= $width && $sizes = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 1362px) 62vw, 840px';
In plain english this says "If $width is larger than or equal to 840, set $sizes to the new custom string on the right. Computer logic markup can get pretty absurd.
The next several lines are variations on this theme, wrapped in different conditionals and providing various conditions of their own:
if ( 'page' === get_post_type() ) {
840 > $width && $sizes = '(max-width: ' . $width . 'px) 85vw, ' . $width . 'px';
} else {
840 > $width && 600 <= $width && $sizes = '(max-width: 709px) 85vw, (max-width: 909px) 67vw, (max-width: 984px) 61vw, (max-width: 1362px) 45vw, 600px';
600 > $width && $sizes = '(max-width: ' . $width . 'px) 85vw, ' . $width . 'px';
}
And again, to wrap things up, the $sizes attribute is returned to the callback to be applied to all images displayed in the post or page:
return$sizes;
So why all these conditions? Well, different templates and layouts put different limits on the total displayed width of content images and these conditions account for all of them.
The conditions will look different depending on the theme and its layout. In Twenty Seventeen they look like this:
740 <= $width && $sizes = '(max-width: 706px) 89vw, (max-width: 767px) 82vw, 740px';
if ( is_active_sidebar( 'sidebar-1' ) || is_archive() || is_search() || is_home() || is_page() ) {
if ( ! ( is_page() && 'one-column' === get_theme_mod( 'page_options' ) ) ) {
767 <= $width && $sizes = '(max-width: 767px) 89vw, (max-width: 1000px) 54vw, (max-width: 1071px) 543px, 580px';
}
}
Enhance the Responsive Images in Your Themes
Like with everything else WordPress, you can rely on the default functionality or enhance it to get the most out of the application. Spending the time to customize the sizes attributes for the images displayed in your theme will drastically improve the performance of your site and reduce server load because the images loaded will fit the displayed size, not the total width of the viewport.
In the upcoming 2016/17 edition of my popular LinkedIn Learning and Lynda.com course WordPress: Building Themes from Scratch Using Underscores I will go into great detail on how to figure out all the breakpoints and conditionals necessary to get this to work. In the meantime I hope this detailed code breakdown will help you on your way.