Update: The Chrome 89 release in March 2021 resolves this issue, ignoring layout shifts under opacity: 0 (more details).
Currently, Chrome’s implementation of the Layout Instability API treats elements with opacity: 0
as visible, causing unexpected layout shift scores.
If you use opacity: 0
to hide page content until it’s ready, Lighthouse penalizes you for above-the-fold layout shifts.
Here’s the open issue & Chromium bug where others are discussing the problem:
- WICG/layout-instability: layout instability spec needs to clarify whether (and which) unpainted nodes are considered or not
- Chromium Issue 1127474: CLS is reporting on invisible content
What’s Happening?
The Layout Instability API (what Lighthouse uses for scoring CLS) considers any element “visible” as long as its layout position is in the viewport bounds and it’s not display: none
or visibility: hidden
—it ignores opacity
altogether.
This makes sense, as determining true element visibility can get complicated fast. Better to keep the logic simple, right?
But the downside of simple rules: it doesn’t cover all cases. Enter opacity: 0
.
Compare PageSpeed Scores
- Opacity Only: https://demo.tannerhodges.com/vitals/cls/opacity/
- Opacity & Visibility: https://demo.tannerhodges.com/vitals/cls/visibility/
Some sites, for whatever reason, still need to hide their page content as it loads to avoid truly visible layout shifts.
The quickest & easiest workaround is to apply body { opacity: 0 }
until the page is ready to display, then reset that style to display the page.
In the examples above, the visual load is exactly the same.
However, their Cumulative Layout Shift is dramatically different:
- Opacity Only: 0.159
- Opacity & Visibility: 0
The difference? One line of code: visibility: hidden
.
The Quick Fix
Until the Layout Instability API spec and Chrome implementation are updated to treat elements with opacity: 0
as invisible, anyone using opacity: 0
to hide content during page load should also use visibility: hidden
.
Update: Better, remove
opacity: 0
from the body and target specific areas instead, then addvisibility: hidden
to ensure they don’t cause CLS (see below).
Hopefully this is a quick win for anyone wondering why their CLS score sucks.
P.S. Holler at me if I missed anything obvious here, or stated anything incorrectly. I’ll continue to update this post as I learn more.
Update 2020-09-26: First Contentful Paint & Overall Score
It turns out Chrome also ignores opacity: 0
for First Contentful Paint (FCP).
This means that, for sites using opacity: 0
to delay the page rendering, Lighthouse currently reports a much faster FCP than real users actually see (i.e., we’re getting better grades than we deserve 😬).
For some, this may be a shock: adding visibility: hidden
will lower your CLS but can also increase your FCP, causing a lower overall score. Lighthouse weights its scores (5% for CLS and 15% for FCP) so the worse FCP outweighs the better CLS, causing your overall score to drop.
Now what? Well, you’ve got a couple options:
- Keep
opacity: 0
alone and accept your fate. - Add
visibility: hidden
to the body and risk a higher FCP. - Remove
opacity: 0
from the body and target specific areas instead, then addvisibility: hidden
to ensure they don’t cause CLS.
As for me, I’m taking this as an opportunity to improve. Let’s fix both CLS & FCP by hiding specific elements instead of the whole page.
For more on optimizing CLS, check out web.dev: Optimize Cumulative Layout Shift.
Update 2020-12-22: Chrome 89
In a related bug ticket about First Contentful Paint ignoring opacity: 0, Paul Irish shared that Chrome is changing CLS to ignore opacity: 0. This change fixes the issue in the demos I linked to above.
The fix is in Chrome 89 and scheduled for release March 2021.
So mark your calendars everyone: come Spring, our CLS scores will start magically improving.
For reference, here’s a link to the commit in Chromium: https://chromium.googlesource.com/chromium/src/+/e1e1bb4a2731c8e3c47ce4660c91cce0e0ddd4dd.
References
- web.dev: Cumulative Layout Shift (CLS)
- WICG/layout-instability
- WICG/layout-instability: layout instability spec needs to clarify whether (and which) unpainted nodes are considered or not
- Chromium Issue 1127474: CLS is reporting on invisible content
- web.dev: Lighthouse performance scoring
- web.dev: Optimize Cumulative Layout Shift