Blade component for responsive images
Creation of a Blade component to facilitate the creation of responsive images.
After I published my last article, the result of Lighthouse dropped significatively, overall on mobile devices. Looking to the Lighthouse recommendations, one of the problem was that I was serving an image bigger than needed. It recommended to Serve Responsive Images. I didn't have idea about responsive images and how difficult and interesting is that topic.
Responsive images
An over-simplify definition is:
Different images in HTML that work well on devices with widely differing screen sizes, and resolutions.
There are many articles like: MDN Responsive Images and Responsive images done right guide picture srcset that give a detailed explanation about this topic.
How to render responsive images in HTML?
HTML provides two main elements to render images:
According to this article: Responsive images, if you're just changing resolutions use srcset,
if we are just changing resolutions, the best is to use the srcset
attribute of the img element.
"Optimal" dimension of the responsive images
Initially I tried to figure out what are the "optimal" dimensions of the responsive images, but there is not a perfect answer for that. After reading the articles: Responsive images 101 Part 9 Image breakpoints and Applying srcset, choosing the right sizes for responsive images at different breakpoints, I found that we should have more breakpoint at larger sizes. I have created these breakpoints for the moment:
- 320x180
- 640x360
- 880x495
- 1024x576
- 1200x675
- 1760x990
Which width and height should we use?
After having the images for the different breakpoints, I still needed to solve the problem of show an explicit width
and height to avoid the Cumulative Layout Shift (CLS) issue. This part was tricky. This article of Addy Osmani:
Optimize CLS
helped me a lot. Initially, I played with a 1.3 relation between the width and the height, but after read the previous
article I learned that it's very important all the images shared the same ratio. This site calculates the ratios:
Aspect Ratio Calculator. After have the images with the same ratio, I
needed to have a fix width and height of that ratio, but even after doing it, the browser always showed me the
images with the dimensions I set. The problem was that I needed to make the dimension dynamic using CSS!
This is the code of the img element:
<img src="assets/img/originals/webcu-lighthouse.png"
srcset="assets/img/320x180/webcu-lighthouse.webp 320w, assets/img/640x360/webcu-lighthouse.webp 640w, assets/img/880x495/webcu-lighthouse.webp 880w, assets/img/1024x576/webcu-lighthouse.webp 1024w, assets/img/1200x675/webcu-lighthouse.webp 1200w, assets/img/1760x990/webcu-lighthouse.webp 1760w"
alt="Lighthouse results"
class="mb-2 w-full"
width="320"
height="180"
>
The width and height attributes specified have the same ratio that the images, and the TailwindCSS class w-full
makes sure the image expand to all the space available to it.
Resume: In order to add an explicit width and height to responsive images, we need to declare those attributes in the image with the same ratio that the images and adjust the width dynamically with CSS!
This article was very interesting as well: Setting height and width to images is important again. It's about the importance of declaring images with an explicit width and height.
Extracting the responsive images to a Blade component
Like we saw previously, the code of the img element is large, tedious, and prone to error. Then I decided to extract
it to a Blade component:
// components/responsive-images.blade.php
@props(['imageName', 'imageExt', 'altText'])
@php
const DIMENSIONS = [
[320, 180],
[640, 360],
[880, 495],
[1024, 576],
[1200, 675],
[1760, 990],
];
$imageSet = [];
foreach(DIMENSIONS as $dimension) {
$imageSet[] = '/assets/img/' . $dimension[0] . 'x' . $dimension[1] . '/' . $imageName . '.webp ' . $dimension[0] . 'w';
}
$srcset = implode(', ', $imageSet);
@endphp
<img src="{{ '/assets/img/originals/' . $imageName . '.' . $imageExt }}"
srcset="{{ $srcset }}"
alt="{{ $altText }}"
class="mb-2 w-full"
width="320"
height="180"
>
We can use it now in any of our templates like this:
...
// Passing a variable to the component
<x-responsive-images
:imageName="$page->cover_image_name"
:imageExt="$page->cover_image_ext"
:altText="$page->cover_image_alt"
/>
/**
* Passing hardcode values to the component
* Notice the '' inside of the "". It's not a typo :)
*/
<x-responsive-images
:imageName="'Name-Image'"
:imageExt="'png'"
:altText="'Hello World of responsive images'"
/>
Notes
-
The attributes of a Blade component are directly accessible inside a php block
@props(['imageName', 'imageExt', 'altText']) ... @php ... $imageSet[] = ... $imageName . '.webp ' . $dimension[0] . 'w' ... @endphp -
The DIMENSIONS constant could be declared outside of the component.
-
I ordered my images like:
- /assets/img/original
- /assets/img/320x180
...
but it can be any other structure.
Extra bonus: Resize Image script
The resize process can be done in many ways, with a Lambda function in AWS for example. I have decided for the moment, given the actual requirements of the blog, to do it manually. To do it I'm using the library Sharp. It really has surprised me. Here is a copy of the script I'm using actually:
const fs = require("fs")
const sharp = require('sharp');
const dimensions = [
[320, 180],
[640, 360],
[880, 495],
[1024, 576],
[1200, 675],
[1760, 990],
];
const args = process.argv.slice(2)
const [image] = args;
const imageName = image.substring(0, image.indexOf('.'));
dimensions.forEach((dimension) => {
const [width, height] = dimension;
fs.mkdir(__dirname + `/source/assets/img/${width}x${height}`, () => {});
sharp(__dirname + '/source/assets/img/originals/' + image)
.resize(width, height, {fit: 'fill'})
.toFormat('webp')
.toFile(__dirname + `/source/assets/img/${width}x${height}/${imageName}.webp`)
;
})
References
- Serve Responsive Images
- MDN Responsive Images
- Responsive images done right guide picture srcset
- img
- picture
- srcset
- Responsive images 101 Part 9 Image breakpoints
- Applying srcset, choosing the right sizes for responsive images at different breakpoints
- Optimize CLS
- Aspect Ratio Calculator
- Setting height and width to images is important again
- Sharp