htmlcssresponsivelighthouseresponsive-images

How to fix Layout Shift for responsive images that use the picture & source html elements?


A problem I have encountered while working with the <source> element is that despite providing the width and height attributes on both the <source> and the image fallback there is still substantial layout shift which I believe is because I have provided images with multiple dimensions(and as there is a substantial difference in size between them).

Simplified example to illustrate:

<picture>
  <source
  sizes="(min-width: 900px) 900px, (min-width: 640px) 600px, (min-width: 340px) 300px, calc(100vw- 56px)"
  srcset="/images/demo-300.avif 300w, /images/demo-600.avif, /images/demo-900.avif 900w"
  type="image/avif" width="300" height="400" />
  
  <source
  sizes="(min-width: 900px) 900px, (min-width: 640px) 600px, (min-width: 340px) 300px, calc(100vw- 56px)"
  srcset="/images/demo-300.webp 300w, /images/demo-600.webp 600w, /images/demo-900.webp 900w"
  type="image/webp" width="300" height="400" />

  <source
  sizes="(min-width: 900px) 900px, (min-width: 640px) 600px, (min-width: 340px) 300px, calc(100vw- 56px)"
  srcset="/images/demo-300.jpg 300w, /images/demo-600.jpg 600w, /images/demo-900.jpg 900w"
  type="image/jpg" width="300" height="400" />

  <img src="images/demo-300.jpg" width="300" height="400" />
</picture>

I would expect that this would work the same way that providing the intrinsic size of an image as the value of the width and height attributes works with the <img> tag to help browsers properly assign space for the image that will render, even when you use CSS later to modify the size of the image.
However this doesn't work with the <source> tag and I want to know if there are any workarounds.


Solution

  • the error encountering here is that although providing width and height attributes for the sources and the image, there's still layout shift observed, specially when images of different dimensions are loaded. To make sure it doesnot happen we will wrap the in a which has the same ratio and we will add the required css as follows:

    <style>
      .container {
        position: relative;
        width: 100%;
      }
    
      .aspect-ratio-placeholder {
        width: 100%;
        padding-top: calc(400 / 300 * 100%);
      }
    
      .image {
        position: absolute;
        top: 0;
    
    <!-- begin snippet: js hide: false console: true babel: false -->

        left: 0;
        width: 100%;
        height: 100%;
      }
    </style>
    
    <div class="container">
      <div class="aspect-ratio-placeholder"></div>
      <picture class="image">
        <source
          sizes="(min-width: 900px) 900px,
            (min-width: 640px) 600px,
            (min-width: 340px) 300px,
            calc(100vw - 56px)"
          srcset="/images/demo-300.avif 300w,
            /images/demo-600.avif,
            /images/demo-900.avif 900w"
          type="image/avif"
          width="300"
          height="400"
        />
        <source
          sizes="(min-width: 900px) 900px,
            (min-width: 640px) 600px,
            (min-width: 340px) 300px,
            calc(100vw - 56px)"
          srcset="/images/demo-300.webp 300w,
            /images/demo-600.webp 600w,
            /images/demo-900.webp 900w"
          type="image/webp"
          width="300"
          height="400"
        />
        <source
          sizes="(min-width: 900px) 900px,
            (min-width: 640px) 600px,
            (min-width: 340px) 300px,
            calc(100vw - 56px)"
          srcset="/images/demo-300.jpg 300w,
            /images/demo-600.jpg 600w,
            /images/demo-900.jpg 900w"
          type="image/jpg"
          width="300"
          height="400"
        />
        <img src="images/vila-demo-300.jpg" width="300" height="400" />
      </picture>
    </div>