Search code examples
csslighthousecumulative-layout-shift

Lighthouse reports high CLS high even when setting CSS min-height


Cumulative Layout Shift in Lighthouse reports a high number: 0.546

Analyzing the trace I notice that the image block is initially not taking up the required space and pushing the content below down. See image: enter image description here

I'm surprised by this since the container div for the image element has a min-height setting:

CSS

div[itemprop="image"] {
    display: inline-block;
    float: left;
    margin-right: 10px;
    min-height: 400px;
}

#prodimage {
    width: 100%;
    max-width: 400px;
    border: 1px solid #FFF;
}

HTML

<div itemprop="image" itemscope="" itemtype="http://schema.org/ImageObject">                
    <a href="#"><span itemprop="url" content="https://www.example./com/images/10055041.png"></span>
        <picture><source type="image/webp" data-srcset="/images/10055041.webp" srcset="/images/10055041.webp">
            <img id="prodimage" data-src="/images/10055041.png" src="/images/10055041.png">
        </picture>
    </a>
</div>

Solution

  • You need to give your image a height and width in your inline CSS.

    When you place an <img/> element the browser does not know how large it is until it has contacted the server.

    As you have inlined your CSS the browser will render the page purely from the initial request (your page HTML and inline styles).

    Because of this it tries to work out the layout, looks through your inline CSS for size information for the image, can't find any for the width and so does not know how much horizontal space to allocate for the image.

    Then the browser requests the image from your server, finds out the size and knows to allocate 400px by 400px.

    This is where your layout shift occurs, it goes from a 0px wide by 400px tall space to 400px wide by 400px tall (plus 1px border) space.

    You were half way there by allocating a min-height to the image, but you also need to define a min-width for the image.

    As such your CSS should be:

    div[itemprop="image"] {
        display: inline-block;
        float: left;
        margin-right: 10px;
        min-height: 400px;
        min-width: 400px;
    }
    

    A better way to handle images

    As an additional thought, a better option rather than setting defined sizes for images is to use a responsive layout.

    What you do there is add the image to a <div> etc. that has a width defined as a percentage.

    Then you put your <img/> inside that <div> with width: 100%.

    This solves the problem of the browser calculating the width.

    To calculate the height you then add width="400" and height="400" to your <img/>.

    In most browsers they then use this to calculate the image aspect-ratio.

    This let's the browser allocate enough vertical height by multiplying the width by the aspect ratio.

    Example

    In the following example you will notice I set the <img/> width to 40 and height to 40.

    This is to show that it does not matter what you set here as long as they are the correct aspect ratio. (so on a 16:9 image you can set width="16" and height="9" even if the image is 800 by 450).

    The browser follows the following process:

    • How big is the div on screen (on a 660px wide screen it is 200 pixels.).
    • How wide is the image (100% * 200 pixels = 200 pixels).
    • What is the aspect ratio? (width / height => 40 / 40 = 1:1).
    • Multiply the calculated width by the height (200 pixels * 1).

    As such the browser will allocate a 200px by 200px space for the image before it has even started the request so you get no layout shift.

    <style>
       .holder{
           width: 33%;
           background-color: #333;
       }
       img{
           width: 100%;
           height: auto;
       }
    </style>
    
    <div class="holder">
      <img src="https://placehold.it/400x400" width="40" height="40" />
    </div>
        
        <p>text that will not move</p>