Google PageSpeed - LightHouse 6 : CLS (Cumulative Layout Shift) and LCP (Largest Contentful Paint)

Logo

Introduction

LightHouse 6 has been released mid May 2020. Available in command line with Google PageSpeed, Lighthouse 6 is embedded in Chrome Developer Tools since mid July 2020 (Chrome 84).

What’s new in version 6 ? Performance score is updated with 3 new metrics introduced while 3 other metrics are removed.

Performance metrics in version 5 :

Performance metrics version 5

Performance metrics in version 6 :

Performance metrics version 6

In version 6, deprecated metrics are :

  • FMP (First Meaningful Paint) : these metrics were too variant.
  • FCI (First CPU Idle) : not distinct enough from the metrics TTI (Time to Interactive).
  • Max Potential First Input Delay : no longer shown in the report but still available in the JSON result file. It is now recommended to look at the metrics TBT (Total Blocking Time).

3 new metrics are introduced in version 6 and used when computing performance score :

  • TBT (Total Blocking Time) : these metrics are not really new and were already available in Lighthouse 5, but they were not used when computing the score. TBT replaces FCI, FCI was used to measure when a page is ready for user input (clicks or taps), TBT measures how non-interactive a page is.
  • LCP (Largest Contentful Paint) : a measurement of perceived loading experience more realistic. It marks the point during page load when the primary content has loaded and is visible to the user.
  • CLS (Cumulative Layout Shift) : THE most interesting new metrics. It measures the visual stability. It quantifies how much a page’s content visually shifts around. Shifts (dynamic content insertion, images, fonts loads…) may generate a bad user experience.

With LCP and CLS metrics, Lighthouse 6 scoring is more realistic and close to user experience.

Lighthouse 6 | Deprecated, New

This paper focus mainly on the new CLS metrics : how to diagnose the unstable elements ?

Metrics weights - Thresholds

Weights have been also been updated in LightHouse 6. With the new computation rules, performance scores significantly decrease in version 6 compared to version 5 for a page. A tool is available online for comparing and defining objectives : Lighthouse scoring calculator.

Score weights

The metrics TBT, LCP and CLS now account 55% of a page’s performance score in LightHouse 6 :

Metrics Weight Metrics Weight
FCP - First Contentful Paint 15% TTI - Time To Interactive 15%
SI - Speed Index 15% TBT - Total Blocking Time 25%
LCP - Largest Contentful Paint 25% CLS - Cumulative Layout Shift 5%

In future releases, CLS metric weight in the score will probably be revised upwards. Actually it is only 5% as it is still a new somewhat experimental measure.

Quality scores thresholds per metrics are the following :

FCP TTI
SI TBT
LCP CLS

CLS - Cumulative Layout Shift

A bad user experience

The below video published on Web.dev website explains very well what CLS metric measures and how the shifts can cause real damage. A page is loading, the user can click or tap on elements, but unfortunately some content is dynamically added, buttons are then shifted : Oops instead of cancelling an order, the user clicks on the submit button.

Basically, CLS metric measures elements layout shifts that occur : DOM elements dynamically added above existing content, images or videos with unknown dimensions, fonts that render larger or smaller than its fallback, an ad or a widget that dynamically resizes itself…

Score recommendations

CLS measures the sum total of all individual layout shifts scores that occur unexpectedly in the viewport for the entire lifespan of the page. A good CLS score should be less than 0.1.

CLS

To calculate the layout shift score, the browser looks at the viewport size and the movement of unstable elements in the viewport between two rendered frames. The score is a product of two measures of that movement: the impact fraction and the distance fraction. For more informations about the score computation : Web.dev - Layout shift score.

Lighthouse results and CLS

In the Chrome Developer Tools Lighthouse report, the DOM elements that contribute most to the CLS of the page are displayed in the section "Avoid Large Layout Shifts" :

Perf CLS Perf CLS - Contribution

Using Lighthouse in command line, in the JSON result file, the CLS score is stored in the key ['lighthouseResult']['audits']['cumulative-layout-shift'].

            "cumulative-layout-shift": {
                "id": "cumulative-layout-shift",
                "title": "Cumulative Layout Shift",
                "description": "Cumulative Layout Shift measures the movement of visible elements within the viewport.",
                "score": 0.02,
                "scoreDisplayMode": "numeric",
                "displayValue": "1.569",
                …
                "numericValue": 1.5685546391150804
            },

The DOM elements that contribute most to the CLS of the page are listed in the key ['lighthouseResult']['audits']['layout-shift-elements'].

            "layout-shift-elements": {
                "id": "layout-shift-elements",
                "title": "Avoid large layout shifts",
                "description": "These DOM elements contribute most to the CLS of the page.",
                "score": null,
                "scoreDisplayMode": "informative",
                "displayValue": "5 elements found",
                "details": {
                    …
                    "items": [
                        {
                            "node": {
                                "snippet": "<div id=\"col-left\">",
                                "selector": "body > div#wrap > div#col-left",
                                "nodeLabel": "Sur le même sujet\nBases de données Time Series\nInfluxDB v2, prise en main. Prép…",
                                "path": "1,HTML,1,BODY,1,DIV,5,DIV",
                                "type": "node"
                            },
                            "score": 1.2030659115893447
                        },
                        {
                            "node": {
                                "snippet": "<nav class=\"topbar-nav menu\">",
                                "selector": "body > div#wrap > nav.topbar-nav",
                                "nodeLabel": "Sybase\nMS SQL\nOracle\nMySQL\nMariaDB\nPostgreSQL\nTime Series\nNoSQL\nUnix-Linux\nConc…",
                                "path": "1,HTML,1,BODY,1,DIV,3,NAV",
                                "type": "node"
                            },
                            "score": 0.341083267664828
                        },
                        {
                            "node": {
                                "snippet": "<div>",
                                "selector": "body > div#wrap > div",
                                "nodeLabel": "Home  Article | InfluxDB - InfluxDB Server\n   ",
                                "path": "1,HTML,1,BODY,1,DIV,4,DIV",
                                "type": "node"
                            },
                            "score": 0.02320975471193952
                        },
                        {
                            "node": {
                                "snippet": "<a href=\"/\" title=\"SQL Pour Administrateurs & Concepteurs\">",
                                "selector": "div#wrap > header > div.hleft > a",
                                "nodeLabel": "SQL Pour Administrateurs & Concepteurs",
                                "path": "1,HTML,1,BODY,1,DIV,0,HEADER,0,DIV,1,A",
                                "type": "node"
                            },
                            "score": 0.0004195429472025217
                        },
                        …
                    ],
                    "type": "table"
                }
            },

Fine tracking CLS score with Google Chrome Developer Tools (Performance tab)

At the time of writing (end of August 2020), the feature described below is actually only available in Google Chrome Canary, it is not yet released in the official Chrome browser.

Google Chrome Canary is the development version and may be unstable but its installation is independent from an existing Google Chrome distribution.

Google Chrome Developer Tools (Performance tab) includes a new great feature to track layout shifts : layout shifts that occur are displayed in the new timeline "Experience". A score is computed for each layout shift, score added in the CLS metric.

Chrome Dev Tools - Performance Tab - Experience - CLS

In the Summary tab, for each layout shift, in addition to the score and the cumulative score, moves are detailed (locations, sizes) :

Chrome Dev Tools - Performance Tab - Experience - CLS with summary

Hover the mouse pointer over the label "Moved from" and then the label "Moved to" to graphically visualize the layout shift of an element in the page :

Chrome Dev Tools - Performance Tab - Experience - Moved from, moved to

In the summary, another useful information : "Had recent input". Layout shifts that occur within 500 milliseconds of user input have the hadRecentInput flag set, they can be excluded from calculations.

Optimizing CLS score

Quite honestly, the CLS score thresholds set to 0.1 and 0.25 are a challenge, especially if content is added or removed dynamically using Javascript. Poor CLS scores are noticed in the following contexts :

  • images without dimensions
  • ads, embeds and iframes without dimensions
  • dynamically injected content after page loading
  • web fonts
  • actions waiting for a network response before updating DOM

Focus on the elements contributing most to the CLS score of the page.

In the case study below, SQLPAC articles CLS scores are optimized from 1.2-0.9 to about 0.007 in few steps.

Measurements Average CLS Standard deviation
Step 0 : Initial version 100 0.904 0.170
Step 4 : Intermediate step 100 0.187 0.019
Step 5 : Final step 100 0.007 0.000073

Measures are performed using a Python script and stored in a MySQL table. About this program : Measuring and storing pages performance metrics using Google PageSpeed Insights API and Python.

Average, Min, Max and standard deviations are computed using the SQL syntax below (Step 1 in the code sample) :

SELECT url, flag, count(*),
       avg(cumulativeLayoutShift),
       min(cumulativeLayoutShift),
       max(cumulativeLayoutShift),
       stddev_pop(cumulativeLayoutShift)
FROM `perf_pagespeed` 
where flag='Step 1'
group by flag, url
order by date_measure desc

The initial HTML skeleton is the following :

  • <nav class="topbar-menu"> : menus container
  • <ul class="metismenu"> : horizontal menu (one row)
  • <div class="sitemap"> : sitemap
  • <div id="col-right"> * : right column
  • <section> * : box in the right column
  • <div class="js-toc"> * : table of contents

* : containers injected dynamically using Javascript

A drawing is more explicit :

SQLPAC HTML skeleton

height, min-height

When possible, apply a fixed size or a minimal size for containers in which some content is dynamically injected.

CLS score is drastically decreased setting a fixed height for the top menu and the sitemap (sizes defined empirically), as expected the standard deviation also drastically decreases. In the initial state, the standard deviation is near the score to reach (0.1) :

Measurements Average CLS Min CLS Max CLS Standard deviation
Step 0 : Initial version 100 0.904 0.789 1.319 0.170
Step 1 : Menus - Fixed heights
.topbar-nav { height: 92px; }

.topbar-nav > ul.metismenu {
  display:flex;
  flex-direction: row;
  height: 46px;
}
100 0.652 0.417 0.940 0.135
Step 2 : Sitemap - Fixed height
div.sitemap { height: 52px;  }
100 0.586 0.363 0.886 0.135

Great improvements, but the score is still bad following the Google criteria, further more the standard deviation is still greater than 0.1, threshold beyond which the CLS score is no longer considered good.

In the next 2 steps :

  • The right column (#col-right) is no more dynamically inserted by Javascript, the empty container is added in the HTML skeleton and a minimum height is defined.
  • Minimum heights are defined for all boxes injected dynamically in the right column.
  • Added in the HTML skeleton, the table of contents container (js-toc) is also no more dynamically inserted by Javascript. A minimum height is set depending on the number of elements in the table of contents.

Even empty, the greater the containers are predefined in the HTML skeleton with at least minimum heights (min-height), the more the CLS score decreases significantly, as well as the standard deviation.

Measurements Average CLS Min CLS Max CLS Standard deviation
Step 3 : Right column stabilization
<!-- Empty container added in the HTML skeleton -->
<div id="col-right"></div>
#col-right           { min-height: 1000px; }
#col-right section   { min-height: 250px; }
#col-right #topten   { min-height: 800px; }
100 0.325 0.316 0.427 0.027
Step 4 : Table of contents stabilization
<!-- Empty container added in the HTML skeleton -->
<div class="js-toc-wrap js-toc-large"></div>
div[class*="js-toc-wrap"]     { min-height: 160px; }
div[class*="js-toc-medium1"]  { min-height: 210px; }
div[class*="js-toc-medium2"]  { min-height: 260px; }
div[class*="js-toc-medium3"]  { min-height: 320px; }
div[class*="js-toc-large"]    { min-height: 380px; }
100 0.187 0.008 0.255 0.019

Deferred CSS defining properties impacting CLS score (overflow…)

The score is now below 0.2, that’s better but not enough. A good score is below 0.1 ! In the previous steps, all elements that could be optimized have been fixed. So why is the score still too high and near 0.2 ?

It is very hard to find the root cause, the Pagespeed’s JSON results files do not give any clue, they only highlight the elements in the viewport for which a layout shift occurs. Answer : be careful with the CSS property overflow defined in a deferred CSS stylesheet.

In this case study, the property overflow: auto; is applied to the pre and code containers in a deferred stylesheet, loaded after the DOMContentLoaded event, so after the first painting. When page’s repaint occurs, the necessary scrollbars generate layout shifts on the elements in the viewport, even if the scrollbars are very far from the viewport.

All CSS properties that may generate layout shifts in the viewport (overflow…) must be defined in the CSS critical path and not in a deferred way. Great, the score is now 0.007, very good score far away from the threshold 0.1 ! The standard deviation is 0.00007 : the page is stable and its stability is predictable and reliable.

Measurements Average CLS Min CLS Max CLS Standard deviation
Step 5 : overflow property moved from a deferred stylesheet to the critical main CSS stylesheet.
pre > code, pre {
  display: block;
  padding: .25em; 
  margin: .25em 0;
  overflow: auto;
  font-family: Consolas, Monaco, 'Andale Mono',
               'Ubuntu Mono', monospace;
  font-size: 1em;
  line-height: 1.5;
}
100 0.007 0.0067 0.0074 0.000073

Elements without dimensions

No surprise and no mystery, about images, videos, ads, iframes, etc., define the dimensions to avoid layout shifts, especially elements immediately visible in the viewport :

Measurements Average CLS Min CLS Max CLS Standard deviation
First image in the viewport without dimensions
<img src="./images/01.png"
     alt="PostgreSQL Schema">
100 0.0201 0.0210 0.0288 0.0102
First image in the viewport with dimensions
<img src="./images/01.png"
     width="380" height="476"
     alt="PostgreSQL Schema">
100 0.0022 0.0015 0.0024 0.00013

LCP - Largest Contentful Paint

FMP has been found to produce inconsistent results and therefore is removed in the scores computations by LightHouse 6. LCP provides more accurate results and accounts for 25% of the performance score in LightHouse 6. LCP marks the point where the largest content element of a page loads and is visible, giving the user the impression a page has fully loaded. It is the best metric to measure website speed.

For once, Google has set reasonable and realistic thresholds. LCP is good when it is less than 2.5 seconds and considered as poor when greater than 4 seconds.

CLS

Just a quick note about finding the latest LCP element, knowing which element is the latest may be a useful indicator.

LCP is detailed in the performance tab (Chrome Developer tools) : timeline, latest element (type, size…).

LCP Performance tab

The latest element LCP is also available in the Chrome Developer Tools Lighthouse report : section "Largest Contentful Paint element"

LCP Lighthouse report

In the JSON result file using LightHouse in command line, the latest element LCP is in the key ['lighthouseResult']['audits']['largest-contentful-paint-element'].

            "largest-contentful-paint-element": {
                "id": "largest-contentful-paint-element",
                "title": "Largest Contentful Paint element",
                "description": "This is the element that was identified as the Largest Contentful Paint. [Learn More](https://web.dev/…)",
                "score": null,
                "scoreDisplayMode": "informative",
                "displayValue": "1 element found",
                "details": {
                    …
                    "items": [
                        {
                            "node": {
                                "snippet": "<ins id=\"aswift_0_expand\" style=\"…\">",
                                "selector": "div#col-left > div#col-right > ins.adsbygoogle > ins#aswift_0_expand",
                                "nodeLabel": "ins",
                                "path": "1,HTML,1,BODY,1,DIV,5,DIV,0,DIV,0,INS,0,INS",
                                "type": "node"
                            }
                        }
                    ]
                }
            },

CLS and LCP reporting in Google Search Console (webmaster console)

Since early August 2020, the bad and medium CLS and LCP scores are reported in Google Search Console (menu : Enhancements Core Web Vitals).

Once corrections are performed and then submitted to Google Search Console, the validation (or not) is notified approximately 20 days later.

In the results below : the evolution of CLS scores in Google Search Console with the corrections applied and described in the previous case study. The CLS scores were initially very poor and indicative of unstable pages.

CLS/LCP Google Search Console

Conclusion

LightHouse 6 now exposes the very interesting statistics CLS. If you have never taken care of a page’s stability before, like the author of this article, the CLS metrics let us discover new development best practices, knowing now how a bad user experience may have dramatic consequences due to a page’s unstability.

Depending on the code and the site’s complexity, in few steps pages can be stabilized :

  • by predefining, when possible, the containers in the HTML skeleton, even empty, instead of dynamically inserting them using Javascript.
  • by defining fixed or minimal sizes (height , min-height , width , min-width) to the containers.
  • by setting the CSS properties impacting layout shifts (overflow…) in the main and critical CSS stylesheet, not in a deferred way.
  • by setting dimensions for images, videos, iframes, ads…

3 useful tools to optimize CLS scores :

  • Chrome Developer Tools LightHouse tab : reports.
  • Chrome Developer Tools Performance tab : Experience timeline.
  • LightHouse JSON results files (command line).