Responsive images

Following the discussion from https://github.com/roots/sage/issues/1984. Speaking of the usefulness and usage of an image generator like https://github.com/syamilmj/Aqua-Resizer or https://github.com/deliciousbrains/wp-image-processing-queue.

So on large websites you can end up with a lot of space used for nothing, plus the backoffice begins to slow down on every image upload.

This is happening automatically anyway. You are gonna get several images stuffed into the upload directory whether you use them or not.

I don’t think so, since only images requested by the front office when users visit it are generated. From my test, they’re not generated at upload. Only an image compression plugin could slow down the process here. Actually, I even remove all standard image sizes first:

/**
 * https://paulund.co.uk/remove-default-wordpress-image-sizes
 */
function paulund_remove_default_image_sizes( $sizes) {
    unset( $sizes['thumbnail']);
    unset( $sizes['medium']);
    unset( $sizes['medium-large']);
    unset( $sizes['large']);         
    return $sizes;
}
add_filter('intermediate_image_sizes_advanced', 'paulund_remove_default_image_sizes');

And then request new formats when I need it:

@php($pic = aq_resize(get_field('picture')['url'], 1200, null, false, false, true))
<img src="{{$pic[0]}}" alt="{{the_title()}}" width="{{$pic[1]}}" height="{{$pic[2]}}">

Querying image format right from the theme allows more modular usage and scales better.

How do you handle small screens vs. larger screens? 2X retina versions? I just can’t see something like Aqua Resizer handling this without a lot of logic dumped into your Blade file.

It’s pretty straightforward and modular, and yes the “logic” is in the template, because it’s where it’s needed IMO:

@php($thumb = aq_resize(get_field('cover')['url'], 600, null, false, false, true))
@php($retina = aq_resize(get_field('cover')['url'], 1200, null, false, false, true))
<img
  src="{{$thumb[0]}}"
  srcset="{{$thumb[0]}} 1x, {{$retina[0]}} 2x"
  alt="{{the_title()}}"
  width="{{$thumb[1]}}"
  height="{{$thumb[2]}}">

These are simple examples, but I have some images that need different ratio according to the screen sizes, each one generated in @1x and @2x. It’s a lot, but sometimes it’s needed. But if I had to do it the Wordpress way, these N number of images would be multiplied for each format for each image of the site.

Any feedback or idea appreciated :slight_smile:

1 Like

I’ve tried a number of different things to simplify the generation of responsive images. Usually the built-in WordPress stuff isn’t totally sufficient to my needs because of the kind of designs I’m asked to implement. On one of my latest projects I built at system that accepts an array of arguments, applies defaults, and returns a string. This happens at the controller level, so in my Blade templates I just have to do {!! $image !!} to print it out.

The arrays describe all the properties I want my image to have. A beefy one might look like this:

[
  /** 
    * This describes the image that appears in the <picture>'s
    * <img>.
    */
  'id' => 109,
  'size' => [375, 250],
  'crop' => true,
  'hidpi' => true,
  'class' => ['additional', 'class'],
  'attrs' => [
    ['aria-hidden', 'true'],
    ['data-count', '2']
  ]
  /** 
   * These sources are the <source> items in the <picture>.
   * Each of them supports all the same arguments as defined
   * above (that make sense in the <source> context).
   */
  'sources' => [
    /**
     * Defaults are applied to all sources, unless overriden by a
     * particular source definition.
     */
    'defaults' => [
      'crop' => true,
      'hidpi' => true
    ],
   /**
     * The most basic source definition is a size and a query.
     */
    [
      'size' => [680, 300],
      'query' => '(min-width: 36em)'
    ],
    [
      'size' => [1024, 480],
      'query' => '(min-width: 45em)'
    ]
  ]
]

(That’s actually a relatively conservative number of queries compared to what my image calls actually ended up looking like.)

Those obviously take a long time to type out, so I also wrote a little library that collects definitions for images I’ll need in my project. With that, I can call the definition for an image type, and then override only a few properties if I need to.

The arrays of settings are passed to a class that does all the work of constructing the data and generates images on the fly using Fly Dynamic Image Resizer. Then I pass that data directly through a responsive Blade template to generate the HTML:

return App\Template(
  'molecules.responsive', 
  ['image' => (object) $this->data]
);

Ultimately that ends up looking like this:

// controllers/FrontPage.php
public function featuredImage()
{
  $Featured = new ResponsiveImage(
    ResponsivePresets::getPreset(
      'featured',  // name of the preset I want
      [
        'id' => get_post_thumbnail_id(), // id of the image to use
        'class' => ['frontPage__featured'], // additional setting
      ]
    )
  );

  return $Featured->generateResponsiveImage();
}
<!-- views/front-page.blade.php -->

<header class="frontPage__header">
  {!! $featured_image !!}
</header>

This allows me a great deal of flexibility in how my images look and behave, and uses presets to keep arguments relatively DRY. It also keeps all of the logic out of my view templates. I prefer to keep even things like arguments in my controllers, but if you wanted to adapt a similar system to allow for calling specific images directly from templates, it wouldn’t be difficult to write a static method on App that would do just that.

Although this system minimizes the number of images generated by using Fly Dynamic Image Resizer to only build the images I need, it does still have some problems:

  1. Still ends up generating quite a few images.
  2. Images must be generated on the first “hit,” which is slow.

In my use cases I’ve determined that these constitute relatively minor problems, but some of that is because of scale: On a larger scale, they would become more onerous. I’ve strongly considered (but have yet to implement) a simpler system that uses a service like Cloudinary or imgix. They can automatically handle a lot of responsive logic and image generation, and with a little work you could build a solution that would be transparent and fall back nicely if you decided to move away from them.

4 Likes

Instead of unset for all the image sizes and the filter, I think you can just do remove_theme_support( 'post-thumbnails' ); during after_theme_setup. This will stop WordPress from auto-creating resized images.

That said, you have effectively disabled all image sizes. So you have to hand-craft every archive thumbnail, cover image, etc.? Lot of work.

Also, one additional tweak needs to added. WordPress will still try to filter the_content and add srcset markup on the frontend. You will need to disable that as well.

// disable srcset on frontend
add_filter('max_srcset_image_width', create_function('', 'return 1;'));

And yes, them Blade files will be messy! :wink:

I would put all of that logic into a dedicated class to handle the output. Something along the lines of Justin Tadlock’s Get The Image plugin. Instead of grabbing the WordPress generated sizes, you rework to your own function methods. Perhaps even make it config-driven.

You can then add some of the basic filters for generating thumbnails, feature image handling, etc. and not need to touch your Blade templates in some cases.

Now your class is easily injected with something like this

@php( \App\Lib\ImgGenerate::cover( $context, $args ))

You would use $context for the post-type and may not even need $args. Your class has enough info to generate the img markup (or picture). Plus you have all of that mess in one file, instead of across all of your templates.

Thanks @alwaysblank for your feedback. Interesting approach, maybe too complicated for my use case though. Really like the plugin you mentioned, will give it a try, it has more options and more recent support too. The way dimensions can be shortcut with a name is great, and could be sufficient to manage “logic” somewhere else that in the templates, even if I don’t bother.

That said, you have effectively disabled all image sizes. So you have to hand-craft every archive thumbnail, cover image, etc.? Lot of work.

Not at all, it’s just one line of PHP for each dimension. A fair price for a full custom solution IMO. I guess you have to write this a way or another, anyway…

And yes, them Blade files will be messy! :wink:

I prefer clarity rather than over-engineering :slight_smile: I think having some PHP lines just above the actual img tag is better in terms of maintainability, and not that bad even for a purist!

Now your class is easily injected with something like this

Looks pretty much overkill to handle custom dimensions, but could be necessary for wider and more complex projects.

Thanks for the bits of code!

I bumped into this old topic;
We made the ResponsivePics wp plugin exactly for this use case. I wrote an article about it:

Hope this helps anyone!

5 Likes

I now prefer to manually optimize Images integral to the theme (using a tool like squoosh),
for jpeg/png; webp and avif for different resolutions. I save the CLI commands from squoosh so I can re-use them if I want and store the settings.