性能指标

CLS

Cumulative Layout Shift 累积布局偏移

最常见的影响CLS的分数的有:

  • 未指定尺寸的图片

  • 未指定尺寸的广告、嵌入元素、iframe

  • 动态插入内容

  • 自定义字体(引发FOIT/FOUT)

  • 在更新DOM之前等待网络响应的操作

未指定尺寸的图片

总而言之:在

历史
在web的早期,开发者会给 标签加上 width 和 height 属性,以确保浏览器开始获取图片之前可以分配好空间,这样可以减少 reflow 和 re-layout。

你也许会注意到这两个属性没有带单位。这些像素尺寸会确保保留640 * 360的区域。图片最终会平铺在这个区域,不管原始尺寸是否一致。

当响应式设计来临的时候,开发者开始忽略 width 和 height,开始使用css来调整图片大小。

这种方法的缺点是,只有图片下载的时候,浏览器才知道图片的宽高并且分配好空间。图片下载完了,每张图片出现在屏幕上的时候,页面都会 reflow 一次,会导致页面频繁的往下弹。这对于用户体验来说非常不友好。

因此而诞生了 aspect ratio。aspect ratio 是图片的宽高比。比如,x:y的宽高比,指的是宽度x单位,高度y单位。

这也意味着只要我们知道宽高之一,就能计算出另一个属性。对于一个16:9的宽高比而言:

如果图片有360px的高度,则宽度为 360 x (16 / 9) = 640px

如果图片有640px的宽度,则高度为 640 x (9 / 16) = 360px

现代浏览器最佳体验
现代浏览器可以基于 width 和 height 属性设定默认宽高比,这样就能避免布局偏移。开发者只需要如下设置:

这样一来,图片加载之前,浏览器就可以根据宽高属性分配好空间。图片加载之后,就可以根据宽度或者高度属性,按照宽高比来分配实际空间。

图片的 aspect-ratio 属性在chrome和firefox上已经可以使用了,safari也快支持了。

如果图片位于容器内,可以设置宽度为容器宽度,高度为auto,避免高度被固定位360px。

响应式图片
在使用响应式图片的时候,srcset 定义了图片可以供浏览器选择的尺寸。为了确保图片 width 和 height 可以被设置,每张图片的宽高比必须一致。

有时候我们希望展示图片的剪切部分,比如长图的中间正方形区域,为了视觉好看。

这样一来图片宽高比就不一致了,浏览器可能更需要针对每一个资源设置特定宽高比。但目前还没有好的解决方案,re-layout 依然存在。

未指定尺寸的广告、嵌入元素、iframe
广告
广告是造成布局偏移的罪魁祸首之一。经常性,这些广告会有动态尺寸,这样会导致糟糕的用户体验,当你在往下浏览页面的时候,广告突然插入一些可见内容。

在广告的生命周期里,很多点可以导致布局偏移:

广告容器插入到dom的时候

本站代码调整广告容器尺寸的时候

广告代码库加载的时候(导致容器尺寸改变)

广告内容填充容器的时候(如果最终广告的尺寸不一样,导致容器尺寸变化)

好消息是网站可以采用最佳体验,来减少布局偏移。

为广告位静态保留空间。

换句话说,在广告代码库加载之前,就给容器加好样式。

如果要在内容流中插入广告,在插入之前确保通过保留尺寸来消除布局偏移。如果这些广告在屏幕外加载,则没有这个问题。

在视图顶部插入非粘性广告的时候要特别注意。

避免折叠预留的空间,如果广告没有返回,可以在该空间展示占位符。

通过预留广告所需最大尺寸,来避免布局偏移。

这很有效,不过如果广告很小,可能会有大片空白。

根据历史数据,给广告加上合适的尺寸。

如果广告不太可能填满,一些网站会发现在初始的时候折叠广告位可以减少布局偏移。很难做到每一次都能给广告位精准的尺寸,除非这个广告是你自己提供的。

为广告位静态保留空间
给广告容器设置固定的样式,避免代码库加载的时候,重新调整广告的尺寸。

要额外注意一下小尺寸的广告,如果预留很大的空间,会导致大片空白。

避免在视图顶部插入广告
根据CLS的计算规则,在顶部插入广告比在中间插入,造成的影响更大。

嵌入元素和iframe
可嵌入的挂件可以允许你在页面上嵌入web内容(例如,youtube视频、谷歌地图、社交媒体的帖子等)。这些嵌入元素可以采用多种形式。

html fallback,然后js将该fallback转换成嵌入元素

内联html代码块

iframe嵌入

这些嵌入通常不会事先知道嵌入的大小(例如,社交媒体帖子,是否包含图片?视频?或者多行文本?)。结果就是提供嵌入元素的平台经常无法保证预留足够的空间,导致布局偏移。

为了应对这种情况,你可以通过提前计算嵌入元素的足够空间,以最小化CLS。以下工作流可以参考:

使用开发者工具检查最终嵌入的高度

一旦嵌入元素加载,iframe容器根据内容重新调整尺寸。

记下尺寸,并相应设置嵌入元素占位符的样式。你可能还会用到媒体查询来考虑不同的因素。

动态内容
总而言之,避免在已存在的内容上方插入新内容,除非为了响应用户交互。这样可以保证任何布局偏移都是可预期的。

你可能经常会遇到从顶部或者底部弹出的一些内容。这经常发生在banner或者表单的地方,让页面的剩余内容产生偏移。

“注册即可领取会员大礼包!”

“最近发表的文章”

“安装我们的APP”

“我们还在接受订单”

“GDPR提示,是否允许使用cookie”

如果你需要展示以上的UI内容,请提前预留好空间,避免产生布局偏移。

自定义字体(引发FOIT/FOUT)
下载并渲染自定义字体会引发布局偏移,通过以下两种方式:

fallback字体切换到新字体(FOUT - flash of unstyled text)

从不可见变成可见,因为新字体的渲染缘故(FOIT - flash of invisible text)

以下工具可以帮你最小化影响:

font-display 属性可以让你修改自定义字体的渲染表现,通过使用可选值:auto, swap, block, fallback 和 optional。不幸的是,除了 optional 之外的属性都会引发 re-layout,通过以上的其中一种方式。

Font Loading API 可以减少获取必要字体的时间。

Chrome 83版本之后,可以采取以下方案:

针对关键字体使用 ,提高优先级,让字体下载有更高概率赶在fcp之前,这样就能避免布局偏移。

和 font-display: optional 结合使用。

动画
总而言之,优先考虑 transform,而非会影响布局改变的属性。

在更新DOM之前等待网络响应的操作
尽可能的在网络请求时,给一个loading,或者占位符提示,避免用户在这段时间内进行操作。

开发者工具
可以使用lighthouse和performce检测CLS。

总结

图片的尺寸,以及其他嵌入元素的尺寸,最开始就设定好,或者预留足够空间,这样可以有效避免布局偏移。

利用图片宽高比的属性,可以在优化CLS的同时,做响应式布局。

尽可能不要往已存在内容上方添加新内容。

web字体尽可能早的加载,避免产生FOIT和FOUT

与UI同事配合在交互上避免布局偏移

参考
https://web.dev/optimize-cls/