Blog home

how to LQIP CLS optimization

CLS optimization and LQIP implementation

A ready to use HTML / CSS template for CLS optimization and LQIP implementation.

CLS optimization and LQIP implementation

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 :

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

In the following, we will use our test domain: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:

A broken aspect-ratio.
A broken aspect-ratio.

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:

A fixed aspect-ratio.
A fixed aspect-ratio.

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.

A very lightweight preview.
A very lightweight preview.

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).

Preview is visible through transparency.
Preview is visible through transparency.

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 to 1)
  • the object-fit and its equivalent for the placeholder with --twic-mode (default set to cover)
  • the transition duration with --twic-duration (default set to 400ms)
<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.