Skip to content

Instantly share code, notes, and snippets.

@megclaypool
Last active May 7, 2024 17:23
Show Gist options
  • Save megclaypool/ca94db8163babe97f29a520ad5237cca to your computer and use it in GitHub Desktop.
Save megclaypool/ca94db8163babe97f29a520ad5237cca to your computer and use it in GitHub Desktop.
Responsive Images (with srcset and sizes) in WordPress (PHP) This is for use in a php template, taking advantage of built-in WP image functions to generate a fancy responsive image tag, complete with srcset and sizes. Set a variable equal to the res

This is Jason's Responsive Background Image Twig Macro translated into PHP for use with ACF image fields:

<?php function responsive_ACF_background_image($image, $display_style, $css_path) {
  $retina_style = $display_style . "2x";
  echo "<style>
      $css_path {
          background-image: url(\"" . $image[sizes][$display_style] . "\");
      }
      @media
      (-webkit-min-device-pixel-ratio: 2),
      (min-resolution: 2dppx) {
        $css_path {
            background-image: url(\"" . $image[sizes][$retina_style] . "\");
        }
      }
      @media
      (min-resolution: 2dppx) and (max-width: 485px) {
        $css_path {
            background-image: url(\"" . $image[sizes][$retina_style] . "\");
        }
      }
      @media
      (max-resolution: 1.99dppx) and (max-width: 485px) {
        $css_path {
            background-image: url(\"" . $image[sizes][$display_style] . "\");
        }
      }
  </style>";
} ?>

This is Jason's Responsive Background Image Twig Macro translated into PHP for use with Beaver Builder image fields:

<?php
function responsive_BB_background_image($image, $display_style, $css_path) {
  $retina_style = $display_style . "-2x";

  echo "<style>
      $css_path {
          background-image: url(\"" . wp_get_attachment_image_url($image, $display_style) . "\");
      }
      @media
      (-webkit-min-device-pixel-ratio: 2),
      (min-resolution: 2dppx) {
        $css_path {
          background-image: url(\"" . wp_get_attachment_image_url($image, $retina_style) . "\");
        }
      }
      @media
      (min-resolution: 2dppx) and (max-width: 485px) {
        $css_path {
          background-image: url(\"" . wp_get_attachment_image_url($image, $retina_style) . "\");
        }
      }
      @media
      (max-resolution: 1.99dppx) and (max-width: 485px) {
        $css_path {
          background-image: url(\"" . wp_get_attachment_image_url($image, $display_style) . "\");
        }
      }
  </style>";
}
?>

Here's a good CSS Tricks article on Responsive Images. The upshot is that srcset gives the browser different sizes of the same image to choose from. Sizes lets you tell the browser how wide you expect the image to be at various screen widths. If you don't declare sizes, the browser will just assume that the image will be displayed at 100vw at all screen sizes. So if you know that at 768px and up, this image will only occupy half the screen width, declaring that in your sizes attribute can save wide-screen devices from downloading too-large image sources.

You can generate a very basic image tag by hand using something like:

<?php
if( get_field('image_field_name') ) { ?>
  <img src="<?php echo get_field('image_field_name')[url]; ?>" alt="<?php echo get_field('image_field_name')[alt]; ?>">
<?php } ?>

BUT that completely misses the awesome automatic srcset generation that WP will do for you! There are a couple of cool functions built in to WP that set up an img tag with a srcset so browsers get the proper size image responsively!!! (In addition to setting the alt text and a bunch of other stuff too :)

They are wp_get_attachment_image() and get_the_post_thumbnail()

(Note: I need to explore the exciting wp_prepare_attachment_for_js($attachment_id) function -- it gives access to all the things.)

They accept almost the same arguments:

get_the_post_thumbnail( int|WP_Post $post = null, string|array $size = 'post-thumbnail', string|array $attr = '' )    
wp_get_attachment_image( int $attachment_id, string|array $size = 'thumbnail', bool $icon = false, string|array $attr = '' )

Note that wp_get_attachment_image() takes an extra $icon argument. I've never used it, but don't forget to pass false or '' or null as your third argument or it'll assume your attributes are the $icon and try to apply them :P
Also note that get_the_post_thumbnail() takes the post id as its first argument, and returns the featured image of that post. If your image is stored in another field (such as an ACF field), you'll want to use wp_get_attachment() instead. It takes the attachment_id (<-- different from the post_id!) and returns the image stored in that field.

Here's an example of a basic srcset of an ACF image field that's set to return an image ID:

<?php
if( get_field('image_field_name') ) {
  echo wp_get_attachment_image( get_field('image_field_name'), 'wp_image_size' );
} ?>

And here's an example of a basic srcset of an ACF image field that's set to return an image array:

<?php
if( get_field('image_field_name') ) {
  echo wp_get_attachment_image( get_field('image_field_name')['id'], 'wp_image_size' );
} ?>

This will return an image tag with a srcset that includes all the all the wp image sizes that exist for that photo that have the same proportions as the wp image size that you selected. Since Rootid is using a function that automatically generates x2 (and sometimes x3) versions of each image size, unless the client uploads a ridiculously small original (WP won't crop images that are smaller than either dimension of your image size definition), we're going to have a few srcsets to play with :)

Both wp_get_attachment_image() and get_the_post_thumbnail() can optionally accept $attr, which allows you to pass sizes to your image. (You can pass all sorts of attributes, like classes too. Just add another element to your attributes array, such as 'class' => 'my_awesome_class_name' )

For the INM site, we had a listing page with listing items 4 across on screens > 992px, 3 across on screens > 768px, 2 across on screens > 576px, and full-width on smaller screens. So small tablets needed a higher resolution image than big-screen devices! Using the sizes attribute, I could let the browser know how large the image was going to be to save downloading a larger image than necessary.

Here's an example using wp_get_attachment_image():

$listing_item->image = wp_get_attachment_image( get_post_thumbnail_id($listing_item->id), 'squarish', false, [ "sizes" => "(min-width: 992px) 25vw, (min-width: 768px) 33vw, (min-width: 576px) 50vw, 100vw"]);

Upon reflection, since the image that I was accessing is the featured image, it would have been easier to use get_the_post_thumbnail() like so:

$listing_item->image = get_the_post_thumbnail( $listing_item->id, 'squarish', [ "sizes" => "(min-width: 992px) 25vw, (min-width: 768px) 33vw, (min-width: 576px) 50vw, 100vw"]);

Either way, the html that is produced is identical. Here's an example of one image generated with this code:

<img width="200" height="161" src="http://institute-for-natural-medicine.test/wp-content/uploads/2018/10/EleanorWarshaw-200x161.jpg" class="attachment-squarish size-squarish wp-post-image" alt="" sizes="(min-width: 992px) 25vw, (min-width: 768px) 33vw, (min-width: 576px) 50vw, 100vw" srcset="http://institute-for-natural-medicine.test/wp-content/uploads/2018/10/EleanorWarshaw-200x161.jpg 200w, http://institute-for-natural-medicine.test/wp-content/uploads/2018/10/EleanorWarshaw-400x322.jpg 400w, http://institute-for-natural-medicine.test/wp-content/uploads/2018/10/EleanorWarshaw-600x483.jpg 600w" title="">

Using WordPress Responsive Images with Advanced Custom Fields

{% if img is iterable %}
<img
{{ img.lazy_load ? 'class="lazyload"' : ''}}
src="{{ img.src }}"
alt="{{ img.alt }}"
{{ img.lazy_load ? "data-srcset" : "srcset" }}="{{ img.srcset }}"
{{ img.width ? 'width="' ~ img.width ~ '"' }}
{{ img.height ? 'height="' ~ img.height ~ '"' }}
sizes="{{ img.srcset_sizes }}">
{% else %}
<img
class="{{ img_lazy_load ? 'lazyload' : '' }}"
src="{{ img_src }}"
alt="{{ img_alt }}"
{{ img_lazy_load ? "data-srcset" : "srcset" }}="{{ img_srcset }}"
sizes="{{ img_sizes }}">
{% endif %}
function image_to_srcset($image_id, $wp_image_size, $srcset_sizes = '100vw') {
$temp['srcset'] = wp_get_attachment_image_srcset( $image_id, $wp_image_size, true );
$temp['alt'] = get_post_meta($image_id, '_wp_attachment_image_alt', true);
$temp['src'] = wp_get_attachment_image_src($image_id, $wp_image_size)[0];
$temp['caption'] = wp_get_attachment_caption($image_id);
$temp['srcset_sizes'] = $srcset_sizes;
$temp['width'] = wp_get_attachment_image_src($image_id, $wp_image_size)[1];
$temp['height'] = wp_get_attachment_image_src($image_id, $wp_image_size)[2];
$temp['id'] = $image_id;
return $temp;
}

Responsive Background Image with ACF

function biscuit_responsive_hero_image_css(){
 
$image = get_field( 'hero_page_image' );
// Get the Image from ACF, then set a big and small version of it from predefined custom image sizes
$big_image_output = wp_get_attachment_image_url( $image, 'hero-slide'); // Use ACF Image ID not Array or URL
$small_image_output = wp_get_attachment_image_url( $image, 'mpu-medium');
// Add it to a custom CSS file that is already being enqueued. The handle ('hero-image-css' in this case), must match below and in the enqueue_style line.
$custom_css = "
/* Mobiles */
    .hero-image-bg{
background-image:url('{$small_image_output}');
}
/* Desktop */
@media screen and (min-width: 768px) {
    .hero-image-bg{
       background-image:url('{$big_image_output}');
      } 
} ";
wp_add_inline_style( 'hero-image-css', $custom_css );
}
add_action( 'wp_enqueue_scripts', 'biscuit_responsive_hero_image_css' ,30);

Make sure the image_to_srcset function (below) is in functions.php so it's unversally accessible.

Use it in your php template to produce a nice sourceset that takes advantage of all the cool tricks you can read about in the longer How To below.

ex:

$context['hero_image'] = image_to_srcset(get_field('hero_image')['id'], 'home_hero', '100vw');

generates the following array:

array (size=8)
  'srcset' => string 'http://zen-hospice.test/wp-content/uploads/2019/08/2400-1440x480.jpeg 1440w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-576x192.jpeg 576w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-768x256.jpeg 768w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-992x331.jpeg 992w' (length=300)
  'alt' => string '' (length=0)
  'src' => string 'http://zen-hospice.test/wp-content/uploads/2019/08/2400-1440x480.jpeg' (length=69)
  'caption' => string '' (length=0)
  'srcset_sizes' => string '100vw' (length=5)
  'width' => int 1440
  'height' => int 480
  'id' => int 160

In the twig template, this is fed into image-srcset.twig and the final output is:

<img src="http://zen-hospice.test/wp-content/uploads/2019/08/2400-1440x480.jpeg" alt="" srcset="http://zen-hospice.test/wp-content/uploads/2019/08/2400-1440x480.jpeg 1440w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-576x192.jpeg 576w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-768x256.jpeg 768w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-992x331.jpeg 992w" width="1440" height="480" sizes="100vw">

Note that this is an alternative to the built-in wp_get_attachment_image(). Here's an example of that:

$context['hero_image'] = wp_get_attachment_image(get_field('hero_image')['id'], 'home_hero', false, ["sizes" => "100vw"]);

which produces:

<img width="1440" height="480" src="http://zen-hospice.test/wp-content/uploads/2019/08/2400-1440x480.jpeg" class="attachment-home_hero size-home_hero" alt="" sizes="100vw" srcset="http://zen-hospice.test/wp-content/uploads/2019/08/2400-1440x480.jpeg 1440w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-576x192.jpeg 576w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-768x256.jpeg 768w, http://zen-hospice.test/wp-content/uploads/2019/08/2400-992x331.jpeg 992w" />

which is... functionally identical on this example... Huh. I'm not sure if they've updated the WP function since the last time I looked at it or what... Oh well. My code still lets me mess around with the image classes in the template, if I want to :P

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment