WP Responsive Images

If you have a site that doesn’t have a lot of images or mostly full width images, then just using Adaptive Images is probably the way to go.

Where Adaptive Images falls short for me is in situations where an image might be half of the site’s width on large screens but then full width on smaller screens.  In this case a better approach is take advantage of the HTML “srcset” attribute for images, which is gaining cross browser/device support.  You can read more about it here.  The gist is that you can define what how many sizes of a particular image are available to the browser and how much of the browser’s width that image will be taking up at different sizes.

Right now there’s a WordPress plugin that implements srcset but I found that I could save some time by combining this plugin with some functions.

<?php 
/* --------------------------------------------
Off the bat we want to define both a retina and non-retina
version of an image with this function...
----------------------------------------------*/	
function add_image_size_with_retina_version($name, $width, $height, $force_dimensions)  {	
	add_image_size($name, $width, $height, $force_dimensions);
	add_image_size($name.'@2x', $width*2, $height*2, $force_dimensions);
}

/* --------------------------------------------
Then, we want to define the minimum breakpoints 
that all our images will respond to
bootstrap, for example, has the 
following minimum breakpoints...

Large devices Desktops (≥1200px)
Medium devices Desktops (≥992px)	
Small devices Tablets (≥768px)	
Extra small devices Phones (<768px)	

this corresponds to the $min_breakpoints 
in the function below...

When defining image sizes, we explicitly define
a width and sometimes a height if we want the
aspect ratio of the image to be preserved.  
----------------------------------------------*/	
// ACTION: customize this if you want
$min_breakpoints = array(1200, 992, 768);

$all_images_sizes = array();

function ultimate_image_definition($name, $ratio = 'anyHeightOk', $widths)  {
	global $min_breakpoints, $all_images_sizes;	
	/* --------------------------------------------
	$widths is an array of 4 width numbers 
	(in pixels) that correspond to the 3 "$min_breakpoints"
	(defined above) plus the range from "0" to the
	smallest $min_breakpoint 
	----------------------------------------------*/			

	$i = 1;
	foreach($widths as $width) { 

		/* --------------------------------------------
		Here we're just tweaking the name of the image
		for each version of it...
		----------------------------------------------*/
		switch ($i) {
			case 1: 
				$appended_text = '';
				break;
			case 2: 
				$appended_text = '_md';
				break;
			case 3: 
				$appended_text = '_sm';
				break;
			case 4: 
				$appended_text = '_xs';
				break;
		}		
		$name_text = $name . $appended_text;

		// get the height and forced dimensions figured out...
		if('anyHeightOk' == $ratio)  {
			$height = 9999;	
			$force_dimensions = false;
		}		
		else  {
			$height = $width*$ratio;
			$force_dimensions = true;
		}

		// add the regular version + retina version for each version of the image
		add_image_size_with_retina_version($name_text, $width, $height, $force_dimensions);

		$i++;
	}

	// we want to save these sizes for later...
	$all_images_sizes[$name] = $widths;
}
/* --------------------------------------------
this $widths array could be defined with some 
full-width defaults if you want
these widths are the full content widths 
also taking bootstrap as an example
(bootraps ".container" inner width at each of these breakpoints)
plus "480" since that is the landscape width of a lot of phones
----------------------------------------------*/	
$full_content_widths = array(1040, 970, 750, 450);

// now we define the images!
ultimate_image_definition('letterbox', 9/16, $full_content_widths);
ultimate_image_definition('square_small', 1, array(380, 323, 250, 450));
ultimate_image_definition('fullAnyHeight', 'anyHeightOk', $full_content_widths);


/* --------------------------------------------
Now that all the images we need are defined,
we can build the function to output them with the 
correct srcset + sizes attributes.

Baisically all we did so far was get wordpress to generate
a huge variety of versions for each kind of image on the site

But the browser doesn't know which of those versions to load

In order to tell the browser, we need to generate a "sizes" attribute
on the image tag.  This tells the browser, for each range of window sizes,
the image's available space.  

If an image only takes up a third of the available content on the page
for large windows (like "square_small" does), then the browser should know
to load the version of the image that's a third of the content's width.

But if the screen size is small, then the browser will probably need to
load the version of the image that would fill the entirity of that browser size. 

we can start of some default definitions
----------------------------------------------*/	
// $gutter_w is the total gap on the edges of the site on smaller screens
// ACTION: you might need to change this..
$gutter_w = 30;

/* --------------------------------------------
Now we want to let the browser know how much space is available
for a particular image to take up for each of the 4 breakpoints
----------------------------------------------*/
function ricg_sizes($this_images_widths)  {
	global $min_breakpoints, $gutter_w;
	return array(
		'sizes' => array(
		    array(
		      'size_value'  => "{$this_images_widths[0]}px",
		      'mq_value'        => "{$min_breakpoints[0]}px",
		      'mq_name'         => 'min-width'
		    ),
		    array(
		      'size_value'  => "{$this_images_widths[1]}px",
		      'mq_value'        => "{$min_breakpoints[1]}px",
		      'mq_name'         => 'min-width'
		    ),
		    array(
		      'size_value'  => "{$this_images_widths[2]}px",
		      'mq_value'        => "{$min_breakpoints[2]}px",
		      'mq_name'         => 'min-width'
		    ),
		    array(
		      'size_value'  => "calc(100vm - {$gutter_w}px)"
		    )
		)
	);	
}

function ricg_responsive_image($name, $image_id = false)  {
	global $all_images_sizes;
	// if the image id is not specified, then load the featured image's id
	if(false == $image_id)  {
		$image_id = get_post_thumbnail_id();	
	}

	$image_url = wp_get_attachment_image_src($image_id, $name, true); 
	$alt_text = get_post_meta($image_id, '_wp_attachment_image_alt', true);

	$size_args = ricg_sizes($all_images_sizes[$name]);

	return '<img src="'.$image_url[0].'" '.tevkori_get_srcset_string( $image_id, $name ).' sizes="'. tevkori_get_sizes( $image_id, $name, $size_args ) .'" alt="'. $alt_text .'">';
}

/* --------------------------------------------
All done!  Just call an image like this:  
<?php echo ricg_responsive_image('image_name_here'); ?>
----------------------------------------------*/	

Just remember to regenerate your thumbnails if you add these functions later on in a project.