Problem/Motivation
Drupal has supported configurable lazy vs eager image loading for a couple of years, since
📌
Leverage the 'loading' html attribute to enable lazy-load by default for images in Drupal core
Fixed
and related issues.
There can be a trade-off in a couple of situations:
1. Large 'hero' images that are known to be above the fold (e.g. a 'main image' field on nodes) can be configured to load eager, but then it's potentially quite a large file to download.
2. With media embeds in ckeditor5 or views lists, we don't really want editors to have to make the decision. Similarly we don't really have a way in views to show the first six images in a list eager and the next 24 lazy, especially when rendering view modes because it would mess up render caching.
For a lot of sites, things are predictable enough on most pages that what we have so far is a big step beyond everything being loaded eager and covers the majority of situations, but you can still end up with lazy images above the fold and eager images below the fold when things get less predictable, and eager loading images still block.
A possible solution is Low Quality Image Placeholders, which has been around for a technique for a while, but as far as I know not in Drupal very much, only found
https://www.drupal.org/project/imageapi_optimize_lqip →
so far.
The idea of LQIP is to load a very small (about 1kb) low quality image placeholder eagerly, and then when the full image is loaded lazily, it will replace the placeholder without impacting the Largest Contentful Paint score (because it's the same dimensions).
For lighthouse and other LCP tools, because the full image is the same size as the placeholder, only the first image is counted towards LCP because they're the same size.
For actual site visitors (which is who we should prioritise instead of tricking lighthouse algorithms), they see a pixelated or blurry version of the image at first, which comes into focus when the full image is loaded, it's like a re-implementation of progressive rendered JPEGs in HTML. If it happens quickly enough, you won't even notice.
This would give us a third option beyond eager and lazy - lazy with LQIP. If it works well enough, we could make it the default, because it can't go too wrong in either direction compared to eager and lazy.
However LQIP has its own trade-offs.
The oldest article I can find on LQIP (and the first one on google) is from 2017 https://medium.com/@imgix/lqip-your-images-for-fast-loading-2523d9ee4a62
It loads the smaller image as its own file, and while it talks about 'low bandwidth' connections it doesn't discuss latency. Latency can be 500ms or more (slow 4g or middling 3g connections), and then the latency of the request itself can be more of an issue than the actual file size when it downloads. Additionally, adding more eagerly loaded files could mean the lazy loaded files are loaded even later.
So I think implementing LQIP like this would be counter-productive in a lot of situations - it could make things worse.
There are also problems with the actual placeholder images - highly compressed pngs or jpegs look very blocky and 'wrong' to users, so if they actually look at the image before the real one loads, it will seem off.
So I had a thought - what if instead of an image, we used CSS or SVG to approximate the contours of the image, without needing to load a separate file. The placeholdering would be more obvious, but it would be extremely fast and potentially more of an 'honest' placeholder than a super low res jpg. This would then be loaded without any additional http requests, and still might be 'good enough' in terms of approximating the image - arguably better because SVG can blur the image without a large file size.
And it turns out, of course someone else already had the same idea:
https://github.com/denisbrodbeck/sqip
But... it's only implemented in node and Go. It would be possible to write something that has a node and/or Go dependency, but that limits its applicability. There are also web services that support SQIP creation but that's an external dependency that could go down at any time.
I think this gives us two options:
1. base64 encode a low res image style directly in the image tag.
2. See if we can implement SQIP in PHP.
Steps to reproduce
Proposed resolution
I looked for a PHP implementation of SQIP and couldn't find one, however, both GD and imagemagick support getting the colour at a particular pixel location https://www.php.net/manual/en/function.imagecolorat.php / https://www.php.net/manual/en/imagick.getimagepixelcolor.php
So.. I think we could do something like this:
1. Aggressively downsize the original image so that it's highly pixelated. e.g. we could try 25/25 for a 250/250 image, the ratio could be configurable.
2. Build an SVG based on the above - 25X25 would be 625 squares.
2a. we might want to 'compress' the image further, by sorting all of the colours, and generating a colour palette of a subset of these, say 64 colours. This way, adjacent squares with the same colour could be a rectangle instead. It should allow for a higher initial resolution that way.
3. Apply gaussian blur to the SVG, this would bring back some of the contours and colour depth that we've removed, and it will look like 'definitely a placeholder' instead of a suspiciously low-res image - which is the idea behind SQIP.
This can all be done in an image style, but we'd need to queue the image style creation because it won't generally be HTTP-requested - instead we'd inline the SVG in the image tag, reading it off disk when generating the responsive image element.
This makes the initial HTML document a bit bigger, but should only be a few kb on most pages.
It may be that there's a PHP-native way to do this logic, but I haven't found it yet.
Another approach would be to generate a ver
Remaining tasks
There are two separate things to look at:
1. Adding support for inline-LQIP to responsive images, this could be base64 encoded image or SVG, but the config would be the same.
2. Trying an SQIP implementation to see if we can make something that works.
User interface changes
Introduced terminology
SVG Low Quality Image Placeholder.
API changes
Data model changes
Release notes snippet