累积布局偏移优化 CLS 完全指南

什么是布局偏移

一个十几秒的短视频解释清楚。

更详细的解释是：布局偏移指的是在网页上发生突然变化时，页面中的内容位置发生意外移动的现象。这种情况常常让人感到困扰，因为它会导致阅读中断或误操作。布局偏移通常是由于资源异步加载或动态添加到页面上的 DOM 元素导致的。可能的原因包括具有未知尺寸的图像或视频、字体与其备用字体渲染大小不同，或者第三方广告或小部件动态调整大小。

难受的是，网站在开发过程中的功能通常与用户体验有很大不同。个性化或第三方内容在开发中的行为通常与生产环境中不同，测试图像通常已经存在于开发者的浏览器缓存中，本地运行的 API 调用通常非常快，延迟几乎不可察觉。

什么是 CLS

累积布局偏移 CLS（Cumulative Layout Shift）是一个指标。

是对页面整个生命周期中发生的每个意外布局变化的最大布局变化分数的度量。

CLS 通过测量实际用户遇到布局偏移的频率来帮助解决布局偏移问题。它可以帮助开发者了解布局偏移在真实用户中发生的情况，从而采取相应的措施进行修复。

为什么要优化 CLS

布局偏移是一个非常影响用户体验的问题，通过上面那一个简短的视频也能理解。

布局偏移通常会导致意外点击、页面方向的迷失，最终导致用户受挫。用户往往不会逗留太久。有时也会使用户不按照预计的产品流程走。

通常优化好布局偏移能够很好的提高用户粘性、用户停留时间等指标。

Yahoo! JAPAN News 通过降低 CLS 0.2 分，得到如下成果。

如何降低 CLS

图片等媒体元素占位

在图像、视频等媒体资源元素中始终包含宽度和高度大小属性。或用 CSS 中的 min-height、aspect-ratio 或类似的方式保留所需的空间。

aspect-ratio

可以用来直接指定当前元素的比例。

https://developer.mozilla.org/zh-CN/docs/Web/CSS/aspect-ratio

对浏览器的支持：

padding-bottom

如果考虑浏览器支持问题仍然可以考虑使用目前一个被广泛接受的基解决方案 "Padding-Top Hack"。这个解决方案需要一个父元素和一个绝对型的子元素。然后计算出长宽比的百分比来设置为 padding-top。例如：

Copy




Copy
.container {
position: relative;
width: 100%;
padding-top: 56.25%; /* 16:9 Aspect Ratio */
}

.media {
position: absolute;
top: 0;
}

使用不易产生偏移的 CSS

其中 transfrom 表现很好，以下举几个例子。
用例可以在这里找到：https://play.tailwindcss.com/26PxFA6UVI

zoom VS transform: scale

当 zoom 会撑大页面并向右偏移时，transform: scale 只是在原地放大。

margin VS transform: translate

margin 造成父元素变大，transform: translate 只是让当前元素移动。

border VS box-shadow

border 会撑起父元素，而 box-shadow 并不会。

小心你的懒加载

懒加载会引起布局的偏移，如果你在有懒加载长列表的里进行跳转，请小心！
无动画进行跳转，能够一定程度上避免该问题。

小心使用 transition: all

在页面首次加载或者跳转页面时，transition: all 可能会导致元素的 padding 等从参数为 0 开始渲染，照成页面的抖动。

这都是痛：
Commit：表格以及友情链接图标抖动
Commit：修复导航栏抖动问题

标签顺序导致的偏移问题

由于在移动端上优先展示主要内容，因此侧边栏的 markup 位于主要内容的后面；而在更大的屏幕上，则通过设置 CSS order 的方式进行排序，将主要内容移到中间（即第二列），伪代码如下：

Copy
export default function MainLayout(props) {
return (
<Container>

<Left className={css`@media screen and (min-width: breakpoint) { order: -1 }`} />
<Right className={css`@media screen and (min-width: breakpoint) { order: 1 }`} />
</Container>
)
}

浏览器在首次绘制时并没有完整解析 DOM、只知道 <Main /> 的存在、但不知道 <Left /> 或者 <Right /> 的存在，才因此将 <Main /> 渲染进第一列而不是第二列；直到第二次绘制时，浏览器才将 <Main /> 渲染进第二列、将 <Left /> 渲染进第一列。

Chrome 并不是一次完整解析 HTML 的，在以下两种情况下，Chrome 会暂停解析、开始渲染和绘制：

Chrome 解析器在读取了 65535 字节的 HTML 后暂停

Chrome 在遇到 标签后，会继续读取约 50 个「Token」之后暂停

详细了解请看：优化博客的累计布局偏移（CLS）问题

网页跳转与前进后退缓存

默认情况下，所有浏览器都使用 bfcache，但由于各种原因，有些站点不适合使用 bfcache。有关如何测试和识别阻止 bfcache 使用的任何问题的更多详细信息，请阅读 bfcache 文章。

在你离开后，bfcache 将页面保存在浏览器内存中很短的一段时间，所以如果你返回它们，那么它们将完全恢复为你离开时的样子。这意味着完全加载的页面立即可用，而不会出现任何变化。

现在的 SPA 应用也能很轻易的保证路由跳转页面布局的一致性。记住始终保持你的目录和导航栏在页面的固定位置。

字体

在下载和渲染网络字体之前，通常有两种处理方式：

使用网络字体替代备用字体（FOUT—— 未样式化文本的闪烁）。

使用备用字体显示 “不可见” 文本，直到网络字体可用并且文本可见（FOIT—— 不可见文本的闪烁）。

着两种方式都可能导致布局变化。即使文本是不可见的，它仍然使用备用字体进行布局。这意味着使用该字体的文本块以及周围的内容在网络字体加载时会发生布局变化，与 FOUT 的可见字体完全相同。

以下方法可以帮助你最小化这种问题：

使用 font-display: optional 可以避免重新布局，因为只有在初始布局时网络字体可用时才会使用它。

使用匹配度高的备用字体。例如，使用 font-family: "Google Sans", sans-serif; 将确保在加载 "Google Sans" 字体时使用浏览器的无衬线备用字体。如果只使用 font-family: "Google Sans" 而不指定备用字体，将使用默认字体，而在 Chrome 上默认字体是 "Times"，它是比默认无衬线字体的匹配度更差。

使用新的 size-adjust、ascent-override、descent-override 和 line-gap-override API 来尽量减小备用字体和网络字体之间的大小差异，详细信息请参阅 “Improved font fallbacks” 文章。

使用 Font Loading API 可以减少获取所需字体的时间。

使用 尽早加载关键的网络字体。预加载的字体有更高的机会达到首次绘制，这样就不会发生布局变化。

阅读有关字体最佳实践的 “Best practices for fonts” 文章。

使用真正的骨架屏

测量 CLS 分数

生产阶段

Chrome User Experience Report

PageSpeed Insights

Search Console (Core Web Vitals report)

web-vitals JavaScript library

实验阶段

Chrome DevTools

Lighthouse

PageSpeed Insights

WebPageTest

Lighthouse in DevTools

能够针对移动设备和桌面设备生成网页的实际性能报告，并能够提供关于如何改进相应网页的建议。

在本地开发期间从 DevTools 运行 Lighthouse 非常方便。

PageSpeed Insights

应该就是在线版的 Lighthouse。

Performance in DevTools

性能选项卡在 Chrome 的 DevTools 配置文件的所有页面行为在一段时间内记录。时间轴上会出现一个标记为 “Experience” 的图层，突出显示布局的变化和发生变化的元素。

Web Vitals extension

最好将 Web vital 扩展视为查找性能问题的抽查工具，而不是全面的调试工具 —— 这是 Chrome 的 DevTools 中的性能选项卡的工作。

结语

作为一个对自己项目有较高要求的人，平常几乎都会接触到布局偏移优化或者 Lighthouse，只不过之前自己瞎折腾的时候还没有 CLS 这个概念，现在算是对 CLS 有了较为清晰的概念了。
CLS 作为一个非常基础的优化指标，在用户体验上非常重要，任何项目都应该针对 CLS 做优化。

如有勘误，请及时指出，感谢！

参考