Responsive images with srcset in WordPress

As a developer, I am somewhat conservative. I believe strongly in the importance of web standards, and I am reluctant to be an early adopter of any new techniques or, even worse, non-standard workarounds for limitations in existing standards. I’d rather live with the limitations until a proper standard — or at least a de facto standard — takes hold.

One of the latest issues to challenge my approach has been responsive images. I’ve settled into a pattern of going with “1.5x” quality images, trying to strike a balance between quality on large high-res displays and a reasonable file size. But it really doesn’t do either very well.

Today’s issue of A List Apart features an exciting article:

Responsive Images in Practice

Yes! In practice! Let’s do this!

There have been a couple of competing proposals for handling responsive image sets, and I am pleased to see the srcset attribute begin to emerge as the winner. The biggest plus it has for me is that it degrades gracefully for older browsers that don’t support it.

Well, before I had even finished reading the article I started thinking about how I could leverage the build-in image sizing mechanism in WordPress to use srcset. I haven’t looked around — I’m sure someone else has already created a plugin that perfectly nails what I am attempting to do here. And, to be honest, I haven’t extensively tested this code yet, although I did drop it into a site I’m currently working on, just to be sure it doesn’t throw a fatal error and that it actually does render the HTML <img> tag as advertised.

function img_with_srcset($attachment_id, $default_size='medium', $echo=true) {
  $output = null;

  // Get image metadata
  $image = wp_get_attachment_metadata($attachment_id);

  // Set upload directory for image URLs
  $upload_dir = wp_upload_dir();
  $upload_url = $upload_dir['baseurl'] . '/' . dirname($image['file']) . '/';

  // Build array of sizes for srcset attribute
  $sizes = array();
  foreach ($image['sizes'] as $size) {
    $sizes[$size['width']] = $upload_url . $size['file'] . ' ' . $size['width'] . 'w';
  }
  
  // Sort sizes, largest first
  krsort($sizes);

  // Get image alt text
  $image_alt = get_post_meta($attachment_id, '_wp_attachment_image_alt', true);

  // Generate output <img> tag
  if (!empty($sizes)) {
    $output =  '<img srcset="' . implode(', ', $sizes) . '" ' .
               'src="' . $upload_url . $image['sizes'][$default_size]['file'] . '" ' .
               'alt="' . esc_html($image_alt) . '" />';
  }
  
  // Return/echo output
  if ($echo) {
    echo $output;
    return true;
  }
  else {
    return $output;
  }
}

Let’s just examine what’s going on here.

The function takes three input parameters. An attachment ID, a default size (for the old school src attribute), and a boolean for whether to echo the output or just return it.

First, we get the attachment metadata and put it into $image. You can see more about what the wp_get_attachment_metadata() function does here.

Next, we set up the $upload_url variable to be the full base URL to the WordPress uploads directory. That’s because the metadata output only includes the filename of each sized image, not its full URL.

Then we loop through all of the sizes in the metadata output, generating a series of strings containing the image URL and its width, for use in the srcset attribute. We put these into an array because we need to manipulate the list a bit: we need to sort it so the largest images come first, and then later we need to implode() this into a comma-separated string.

Of course we also need the image’s alt text, so we grab that with get_post_meta() which you can read more about here.

Finally, assuming we actually have some size data, we build the <img> tag, complete with srcset attribute! Then we either echo or return it, as determined by the $echo input parameter.

Something else I’d like to try with this is taking it a step further by adding a filter that processes page content, looking for <img> tags, and automatically inserts the appropriate srcset attribute.

There you have it. I welcome anyone who’s reading this to give the function a try in your WordPress site, and let me know how it goes!