how to LQIP CLS optimization
CLS optimization and LQIP implementation
A ready to use HTML / CSS template for CLS optimization and LQIP implementation.
Miguel Beignon April 6, 2023 · 10 min read
In this article, we provide an HTML / CSS template to easily integrate both CLS optimization and LQIP in your websites using TwicPics Native.
As a reminder :
- Low Quality Image Placeholder (LQIP) improves the user experience by reducing the perceived loading time of media.
- Cumulative Layout Shift (CLS) optimization avoids having to suffer these page jumps every time media above the reading area is loaded.
While only CLS optimization has an impact on SEO, these 2 features improve user experience on sites that implement them.
It is therefore important to apply both features. Important but not always obvious.
That's why we propose here to build an HTML / CSS template that should be able to be used in (all ?) many cases.
If you don't want to follow the complete process, you can go to the end of this article: you will find the ready to use template.
And by the way, if you use TwicPics Components, no need for this template: these 2 features are included out of the box.
Before starting
In the following article, we will use TwicPics Native. It will automatically and transparently :
- manage the various API requests depending on the image display context.
- handle the lazy-loading by feeding the
src
tag progressively as the image becomes visible.
The image used in this section is https://assets.twicpics.com/examples/glass-pyramid.jpg (jpg - 2400x3000px - 875kB).
Step-by-step design of our model
https://demo.twic.pics
If you don't have a TwicPics domain yet, you can easily create your own for free.
Displaying the image
Let's start by displaying our image by replacing src
with twic-src
.
<head>
<!-- Twicpics script installation -->
<script src="https://demo.twic.pics/?v1" async defer></script>
</head>
<body>
<!-- Requested: jpg - 2400 x 3000px - 875kB -->
<!-- Received: webP - 2400 x 3000px - 264 kB -->
<img data-twic-src="image:glass-pyramid.jpg" />
</body>
The image we load here is nothing more and nothing less than the compressed version of the master image without any further transformation.
Note that we have already saved 70% of the bandwidth with this simple compression.
On the other hand, we generate CLS and control neither the image's displayed size nor aspect ratio.
Let's frame our image
We will use the aspect-ratio CSS property to remove the CLS and control the aspect ratio of the display area of our image.
All recent browsers now support aspect-ratio
.
If you are targeting older browsers, we will indicate at the end of this article how to use padding-trick instead.
<head>
<!-- Twicpics script installation -->
<script src="https://demo.twic.pics/?v1" async defer></script>
</head>
<style>
.media {
aspect-ratio: 16/9;
width: 100%;
}
</style>
<body>
<!-- Requested: jpg - 2400 x 3000 px - 875kB -->
<!-- Received: webP - 1600 x 900 px - 70.9 kB -->
<img class="media" data-twic-src="image:glass-pyramid.jpg" />
</body>
We should get something like this:
What happened here?
We have removed the CLS, but we get a distorted image. Indeed, TwicPics Native used the default value fill
of the CSS property object-fit to generate an API request with a resize transformation.
To fix this issue, we have to use a TwicPics transformation that returns an undistorted image regardless of the aspect ratio of the display area. In our example, let's choose cover to get a smartly cropped variant.
As TwicPics Native is context aware, we only need to modify our media
class as this:
<head>
<!-- Twicpics script installation -->
<script src="https://demo.twic.pics/?v1" async defer></script>
</head>
<style>
.media {
aspect-ratio: 16/9;
object-fit: cover;
width: 100%;
}
</style>
<body>
<!-- Requested: jpg - 2400 x 3000 px - 875kB -->
<!-- Received: webP - 1600 x 900 px - 116kB -->
<img class="media" data-twic-src="image:glass-pyramid.jpg" />
</body>
We should now get something like this:
We display our image in a reserved space (no CLS). This image is a cropped variant to maintain its aspect ratio.
Let's implement LQIP
We will start by displaying a light version of our image to reduce the perceived loading time. This lightweight version will be replaced by the current version as soon as it is loaded in the browser.
Loading our lightweight image
Here we use the output transformation to generate a preview version of our image.
Since we don't need SEO for this transitional image, we'll load it as a background image in a div
element.
Using TwicPics Native Attributes this is written as follows:
<head>
<!-- Twicpics script installation -->
<script src="https://demo.twic.pics/?v1" async defer></script>
</head>
<style>
.placeholder {
aspect-ratio: 16/9;
background-size: cover;
width: 100%;
}
</style>
<body>
<!-- Requested: jpg - 2400 x 3000 px - 875kB -->
<!-- Received: SVG - 1600 x 900 px - 3.9kB -->
<div
class="placeholder"
data-twic-background="url(image:glass-pyramid.jpg)"
data-twic-transform="*/output=preview"
/>
</body>
You will notice that we have added the placeholder
class, which prevents CLS and distortion in our preview version, as with the original image.
We get a nice super light 3.9kB SVG variant that we just have to assemble with our final image.
First attempt at assembly
In this first attempt, we display a div
with the preview image as a background. Then, inside the div
, we display our final image.
<head>
<!-- Twicpics script installation -->
<script src="https://demo.twic.pics/?v1" async defer></script>
</head>
<style>
.media {
aspect-ratio: 16/9;
object-fit: cover;
width: 100%;
}
.placeholder {
aspect-ratio: 16/9;
background-size: cover;
width: 100%;
}
</style>
<body>
<div
class="placeholder"
data-twic-background="url(image:glass-pyramid.jpg)"
data-twic-transform="*/output=preview"
>
<img
class="media"
data-twic-src="image:glass-pyramid.jpg"
data-twic-transform="*/background=remove"
/>
</div>
</body>
Unfortunately, this does not work with translucent images (notice that we have forced the line using the transformation background = remove).
You can see the preview version through the transparency zones.
Second attempt at assembly
This time, we will take advantage of the TwicPics life cycle to hide the preview version once the final image is loaded.
To do this, we will place the preview and final versions in absolute position within a div
responsible for maintaining the aspect ratio and, therefore, the cls optimization.
<head>
<!-- Twicpics script installation -->
<script src="https://demo.twic.pics/?v1" async defer></script>
</head>
<style>
.media,
.placeholder {
/* preview and final image must stack and fill their container */
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
.media {
object-fit: cover;
}
.placeholder {
background-size: cover;
opacity: 1;
}
.media.twic-done + .placeholder {
opacity: 0; /* hides placeholder once image is loaded */
}
.cls-optimization {
position: relative;
aspect-ratio: 16/9; /* reserves the display size */
width: 100%;
}
</style>
<body>
<div class="cls-optimization">
<img class="media" data-twic-src="image:glass-pyramid.jpg" />
<div
class="placeholder"
data-twic-background="url(image:glass-pyramid.jpg)"
data-twic-transform="*/output=preview"
></div>
</div>
</body>
Here is a Codepen to see it in action.
User experience improvement
Let's add a little effect on the placeholder to smooth the transition between the preview and the final image.
<head>
<!-- Twicpics script installation -->
<script src="https://demo.twic.pics/?v1" async defer></script>
</head>
<style>
.media,
.placeholder {
/* preview and final image must stack and fill their container */
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
.media {
object-fit: cover;
}
.placeholder {
background-size: cover;
opacity: 1;
transition-property: opacity; /* makes transition smooth */
transition-duration: 400ms; /* makes transition smooth */
will-change: opacity; /* makes transition smooth */
}
.media.twic-done + .placeholder {
opacity: 0; /* hides placeholder once image is loaded */
}
.cls-optimization {
position: relative;
aspect-ratio: 16/9; /* reserves the display size */
width: 100%;
}
.cls-optimization img:not([src]) {
/* avoid broken images */
visibility: hidden;
}
</style>
<body>
<div class="cls-optimization">
<img class="media" data-twic-src="image:glass-pyramid.jpg" />
<div
class="placeholder"
data-twic-background="url(image:glass-pyramid.jpg)"
data-twic-transform="*/output=preview"
></div>
</div>
</body>
And here is the result on Codepen:
Let's make our template dynamic
We introduced some CSS variables to make our template versatile and easily reusable. You are of course free to add more.
Here, we chose to make customizable:
- the aspect-ratio with
--twic-ratio
(default set to1
) - the object-fit and its equivalent for the placeholder with
--twic-mode
(default set tocover
) - the transition duration with
--twic-duration
(default set to400ms
)
<head>
<!-- Twicpics script installation -->
<script src="https://demo.twic.pics/?v1" async defer></script>
</head>
<style>
.media,
.placeholder {
/* preview and final image must stack and fill their container */
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
.media {
object-fit: var(--twic-mode, cover);
}
.placeholder {
background: no-repeat; /* useful for --twic-mode:contain */
background-position: center; /* useful for --twic-mode:contain */
background-size: var(--twic-mode, cover);
opacity: 1;
transition-property: opacity; /* makes transition smooth */
transition-duration: var(
--twic-duration,
400ms
); /* makes transition smooth */
will-change: opacity; /* makes transition smooth */
}
.media.twic-done + .placeholder {
opacity: 0; /* hides placeholder once image is loaded */
}
.cls-optimization {
position: relative;
aspect-ratio: var(--twic-ratio, 1); /* reserves the display size */
width: 100%;
}
.cls-optimization img:not([src]) {
/* avoid broken images */
visibility: hidden;
}
</style>
<body>
<!-- default values -->
<div class="cls-optimization">
<img class="media" data-twic-src="image:glass-pyramid.jpg" />
<div
class="placeholder"
data-twic-background="url(image:glass-pyramid.jpg)"
data-twic-transform="*/output=preview"
></div>
</div>
<div class="cls-optimization" style="--twic-ratio:2;--twic-mode:contain;">
<img class="media" data-twic-src="image:glass-pyramid.jpg" />
<div
class="placeholder"
data-twic-background="url(image:glass-pyramid.jpg)"
data-twic-transform="*/output=preview"
></div>
</div>
</body>
Here are examples of use on Codepen where we show:
- the behavior of the template when no variable is set
- how to set them directly in the HTML code
- how to use them through CSS classes
Ready to use version
Here is the ready-to-use version.
You just have to adapt it by using your twicpics domain, and applying it to each of your assets.
<head>
<!-- Twicpics script installation -->
<script src="https://<your-own-twicpics-domain>/?v1" async defer></script>
</head>
<style>
.media,
.placeholder {
/* reset border, margin and padding */
border: none;
margin: 0;
padding: 0;
/* preview and final image must stack and fill their container */
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
.media {
object-fit: var(--twic-mode, cover);
}
.placeholder {
background: no-repeat; /* useful for --twic-mode:contain */
background-position: center; /* useful for --twic-mode:contain */
background-size: var(--twic-mode, cover);
opacity: 1;
transition-property: opacity; /* makes transition smooth */
transition-duration: var(
--twic-duration,
400ms
); /* makes transition smooth */
will-change: opacity; /* makes transition smooth */
}
.media.twic-done + .placeholder {
opacity: 0; /* hides placeholder once image is loaded */
}
.cls-optimization {
overflow: hidden; /* allows border-radius for example */
position: relative;
aspect-ratio: var(--twic-ratio, 1); /* reserves the display size */
width: 100%;
}
.cls-optimization img:not([src]) {
/* avoid broken images */
visibility: hidden;
}
</style>
<body>
<div class="cls-optimization">
<img class="media" data-twic-src="image:<your-asset.jpg>" />
<div
class="placeholder"
data-twic-background="url(image:<your-asset.jpg>)"
data-twic-transform="*/output=preview"
></div>
</div>
</body>
Note: if you look at our CSS classes media
and placeholder
, you will notice that we have reset the border
, margin
and padding
CSS properties. This is deliberate, and it is to avoid propagating any style defined on the img
and div
tags outside our template.
If you want to apply custom styles to your assets, then you should apply them to the external div
, like in this example :
The previous version is functional on recent browsers. But what if we target older versions that do not support aspect-ratio CSS property?
Alternate Padding Trick version
The padding trick is a CSS hack that was used before all recent browsers managed the aspect-ratio CSS property.
Actually, the padding trick has a weakness when used in a grid-type display. In such a case, the specified aspect-ratio is not respected as we can see here:
We have to slightly modify our HTML template to fix this issue by adding an isolation div
around our previous template.
Besides adding this isolation div
, the only other modification needed to our HTML/CSS template involves the cls-optimization
CSS class, in which we have to replace:
.cls-optimization {
...
aspect-ratio: var( --twic-ratio, 1 );
...
}
by
.cls-optimization {
...
padding-top: calc( 100% / var( --twic-ratio, 1 ) );
...
}
Here is the modified Padding Trick version:
<head>
<!-- Twicpics script installation -->
<script src="https://<your-own-twicpics-domain>/?v1" async defer></script>
</head>
<style>
/* required when using the padding-trick */
.isolation {
overflow: hidden;
}
.media,
.placeholder {
/* reset border, margin and padding */
border: none;
margin: 0;
padding: 0;
/* preview and final image must stack and fill their container */
height: 100%;
left: 0;
position: absolute;
top: 0;
width: 100%;
}
.media {
object-fit: var(--twic-mode, cover);
}
.placeholder {
background: no-repeat; /* useful for --twic-mode:contain */
background-position: center; /* useful for --twic-mode:contain */
background-size: var(--twic-mode, cover);
opacity: 1;
transition-property: opacity; /* makes transition smooth */
transition-duration: var(
--twic-duration,
400ms
); /* makes transition smooth */
will-change: opacity; /* makes transition smooth */
}
.media.twic-done + .placeholder {
opacity: 0; /* hides placeholder once image is loaded */
}
.cls-optimization {
overflow: hidden; /* allows border-radius for example */
position: relative;
padding-top: calc(
100% / var(--twic-ratio, 1)
); /* padding trick : reserves the display size */
width: 100%;
}
.cls-optimization img:not([src]) {
/* avoid broken images */
visibility: hidden;
}
</style>
<body>
<div class="isolation">
<!-- makes the template work even in display:grid -->
<div class="cls-optimization">
<img class="media" data-twic-src="image:<your-asset>.jpg" />
<div
class="placeholder"
data-twic-background="url(image:<your-asset>.jpg)"
data-twic-transform="*/output=preview"
></div>
</div>
</div>
</body>
And that's it, we have a working version even with older browsers.
Conclusion
In this article, we have followed the path that led us to create a pure HTML / CSS template based on the power of TwicPics Native.
This template will (by default) provide square assets displayed in cover mode. These assets will not generate CLS and will be revealed to the user through a smooth transition.
Adding CSS variables allows you to configure these assets' aspect ratio and display mode.
You are of, course free, to adapt it to your needs, modify the class names, add other CSS variables, etc.
Two more things before we finish:
- this template works with images, but of course, you can apply it to your short videos
- if you think it would be convenient to create components to automate the use of this template, we have already done it for you here
Hoping that this article will help you to integrate your assets on your websites. Do not hesitate to give us feedback and/or suggestions for improvement.