Improving Performance Perception: On-demand Image Resizing
Over a series of articles, we've been building a sample application --- a multi-image gallery blog --- for performance benchmarking and optimizations. At this point, our application serves the same image regardless of the resolution and screen size it's being served in. In this tutorial, we'll modify it to serve a resized version depending on display size.
Objective
There are two stages to this improvement.
- We need to make all images responsive wherever this might be useful. One place is the thumbnails on the home page and in the gallery pages, and another is the full-size image when an individual image is clicked in the gallery.
- We need to add resizing-logic to our app. The point is to generate a resized image on the fly as it's demanded. This will keep non-popular images from polluting our hard drive, and it'll make sure the popular ones are, on subsequent requests, served in optimal sizes.
Responsive Images?
As this post explains, images in the modern web are incredibly complex. Instead of just <img src="mypic.jpg">
from the olden days, we now have something crazy like this:
<picture>
<source media="(max-width: 700px)" sizes="(max-width: 500px) 50vw, 10vw"
srcset="stick-figure-narrow.png 138w, stick-figure-hd-narrow.png 138w">
<source media="(max-width: 1400px)" sizes="(max-width: 1000px) 100vw, 50vw"
srcset="stick-figure.png 416w, stick-figure-hd.png 416w">
<img src="stick-original.png" alt="Human">
</picture>
A combination of srcset
, picture
and sizes
is necessary in a scenario where you're doubtful that if you use the same image for a smaller screen size, the primary subject of the image may become too small in size. You want to display a different image (more focused on the primary subject) in a different screen size, but still want to display separate assets of the same image based on device-pixel ratio, and want to customize height and width of the image based on viewport.
Since our images are photos and we always want them to be in their default DOM-specified position filling up the maximum of their parent container, we have no need for picture
(which lets us define an alternative source for a different resolution or browser support --- like trying to render SVG, then PNG if SVG is unsupported) or sizes
(which lets us define which viewport portion an image should occupy). We can get away with just using srcset
, which loads a different size version of the same image depending on the screen size.
Adding srcset
The first location where we encounter images is in home-galleries-lazy-load.html.twig
, the partial template that renders the home screen's galleries list.
<a class="gallery__link" href="{{ url('gallery.single-gallery', {id: gallery.id}) }}">
<img src="{{ gallery.images.first|getImageUrl }}" alt="{{ gallery.name }}"
class="gallery__leading-image card-img-top">
</a>
We can see here that the image's link is fetched from a Twig filter, which can be found in the src/Twig/ImageRendererExtension.php
file. It takes the image's ID and the route's name (defined in the annotation in ImageController
's serveImageAction
route) and generates a URL based on that formula: /image/{id}/raw
-> replacing {id}
with the ID given:
public function getImageUrl(Image $image)
{
return $this->router->generate('image.serve', [
'id' => $image->getId(),
], RouterInterface::ABSOLUTE_URL);
}
Let's change that to the following:
public function getImageUrl(Image $image, $size = null)
{
return $this->router->generate('image.serve', [
'id' => $image->getId() . (($size) ? '--' . $size : ''),
], RouterInterface::ABSOLUTE_URL);
}
Now, all our image URLs will have --x
as a suffix, where x
is their size. This is the change we'll apply to our img
tag as well, in the form of srcset
. Let's change it to:
<a class="gallery__link" href="{{ url('gallery.single-gallery', {id: gallery.id}) }}">
<img src="{{ gallery.images.first|getImageUrl }}"
alt="{{ gallery.name }}"
srcset="
{{ gallery.images.first|getImageUrl('1120') }} 11
Truncated by Planet PHP, read more at the original (another 5814 bytes)