总篇228篇 2023年第42篇1.?CLS 诞生背景在深入探讨优化实践之前,让我们先了解一下 CLS(累计布局偏移)及其优化的重要性。我们在浏览网页时可能会遇到这样的情况:当我们正聚精会神地阅读网页内容时,突然发现内容在没有任何预警的情况下被挤到了另一个位置。尤其是当您已经阅读了一小段文字后,不得不费劲寻找原来的阅读位置,这种意想不到的偏移可能会让您感到极度不悦。更糟糕的是,当您准备点击一个链接或按钮时,正巧在手指按下的瞬间,链接突然移位,导致您误点到其他内容。通常情况下,这样的网站会给用户带来极差的体验,甚至瞬间的位移错误交互可能造成一定程度的破坏。 已关注 关注 重播 分享 赞 关闭观看更多更多之家技术已关注分享视频,时长00:120/000:00/00:12 切换到横屏模式 继续播放进度条,百分之0播放00:00/00:1200:12 倍速播放中 0.5倍 0.75倍 1.0倍 1.5倍 2.0倍 超清 流畅 您的浏览器不支持 video 标签 继续观看 页面CLS 优化实践 观看更多转载,页面CLS 优化实践之家技术已关注分享点赞在看已同步到看一看写下你的评论 视频详情 通常,页面元素发生意外偏移的原因包括异步资源加载以及在 DOM 元素上方动态插入新的元素等。具体而言,这些偏移可能源于图片或视频元素尺寸未设置、不受控的第三方广告引入或者小组件自身大小的动态调整。因此,我们需要一个指标来度量意外偏移对用户“视觉稳定性”产生的影响。2.?CLS 定义? CLS (Cumulative Layout Shift) 累计布局偏移指标,通过度量“视觉稳定性”反映用户对页面布局偏移所产生的主观视觉体验。? Google 定义每隔 5 秒作为一个 CLS 监听"时间窗口",并且要求在每个"时间窗口"内,相邻两次偏移的时间间隔小于 1 秒。在单个"时间窗口"内,CLS 值是由多个元素的偏移值累加得到的。如果页面中的元素布局持续发生偏移(持续时间大于 5 秒),则可能会形成多个"时间窗口",每个"时间窗口"的 CLS 值可能不同。在这种情况下,将最大"时间窗口"的 CLS 值作为该页面的最终 CLS 值。如下图:CLS = Max(session window 1,session window 2,session window 3) = 0.1052.1如何对 CLS 评级?谷歌定义页面 CLS 数值应该控制在 0.1 以内,包括移动和桌面设备。为了确保大多数用户达成目标,一个良好的测量阈值为第 75 个百分位数。3. CLS 如何计算3.1不稳定性元素任何位于可视区域内的可见元素,如果其起始位置在两帧之间发生变化,则被视为“不稳定元素”。这些“不稳定元素”用于计算布局偏移。需要注意的是,如果新元素添加到 DOM 或现有元素的尺寸发生改变,只要这些变化不导致其他可见元素的起始位置发生变化,它们就不会被计算为布局偏移。3.2计算公式布局偏移分数 = 影响分数(impact fraction) * 距离分数 (distance fraction)举例 :? 0.07? =? 0.5 * 0.14? 影响分数该计算因子的含义是测量“不稳定元素”对两帧之间的可视区域产生的影响,即可视元素的起始位置在两帧之间发生变化后,两帧中元素可视区域的合集占总可视区域的百分比。? ? 如下图所示:? ? 上图中黄色块 P 标签为“不稳定元素”,其自身元素占可视区域的 50%,在两帧中移动了可视区域高度的 25%,红色虚线表示两帧中元素的可见区域集合,该集合占总可视区域的 75%,因此在本例中影响分数为 0.75。? 距离分数? ? 该计算因子的含义是,测量不稳定元素相对于可视区域在一帧中位移的最大距离(该位移距离可以是水平或者垂直方向,同时存在时取最大者),除以可视区域的最大尺寸维度(尺寸维度可以是宽度或高度,取较大者)。如下图所示:? ? 上图中最大可视区域尺寸维度是高度,不稳定元素位移的距离为可视区域高度的 25%,因此距离分数为 0.25 。? ? 根据公式:布局偏移分数 = 影响分数 * 距离分数,以上图为例 CLS 值为 0.75(影响分数)* 0.25(距离分数)= 0.1875 。3.3举个例子如上图所示,点击 “Click Me!” 按钮时绿色框部分为不稳定元素,发生了向下的位移,其影响范围为红色虚线框部分(底部超出可视区域不做计算),占总可视区域的 50%,即影响分数为 0.5 。距离分数由紫色箭头表示,在高度尺寸维度发生了位移,其位移距离为可视区域高度的 14%,即距离分数为 0.14 。所以布局偏移分数是 0.5 * 0.14 = 0.07 。3.4符合预期的布局偏移布局偏移并不总是坏事。(布局偏移只有在用户并不期望其发生时才算是坏事), 以下两种情况, 不会影响 CLS 分数。(1)由用户发起的布局偏移? ? ? ?用户交互(如单击链接、点选按钮、在搜索框中键入信息等), 500毫秒之后发生的 CLS 都会带有"hadRecentInput"标记, 不会影响 CLS 分数 。注意 : "hadRecentInput" 仅适用于不连续输入事件 (如 tap, click, or keypress), 而连续输入事件不会被标记(如 scrolls, drags, or pinch and zoom gestures)? ? ? ? (2)动画和过渡? ? ? ? 动画和过渡如果做得好,确实是一个在更新页面内容时不让用户感到突兀的好方法。CSS transform 属性可以帮助我们在不触发布局偏移的情况下为元素设置动画:? ? ? ? 用 transform: scale() 来替代和调整 height 和 width 属性。? ? ? ? 如需使元素能够四处移动,可以用 transform: translate() 来替代和调整 top、right、bottom 或 left 属性。4. CLS?测量? ? ? ? 在上部分中对 CLS 的基础理论进行了介绍,并通过相关示例演示了 CLS 值是如何计算的,接下来我们看一下在浏览器中如何通过工具对 CLS 进行跟踪测量。4.1DevTools? ? ? 打开 Chrome DevTools,在 Performance 标签选项卡中点击“录制”按钮并刷新页面,您将得到 CLS 的跟踪信息,大部分情况下您可以通过该功能还原定位线上 CLS 问题。? ? ? ? 如下图:? ? ? ? 点击不同的“红色块”可以查看其对应的 CLS 值及 DOM元素和相关偏移信息。如下图:4.2通过 JavaScript 测量 CLS? ? ? 我们可以使用 JavaScript 和浏览器原生的 PerformanceObserver 来测量 CLS,通过监听 layout-shift 条目,并根据每 5 秒为一个窗口期的最大分数累计规则,对这些监听到的 layout-shift 条目数据进行计算。? ? ? ? ? 实现代码如下:let clsValue = 0;let clsEntries = [];let sessionValue = 0;let sessionEntries = [];new PerformanceObserver((entryList) => { ?for (const entry of entryList.getEntries()) { ? ?// 只将不带有最近用户输入标志的布局偏移计算在内。 ? ?if (!entry.hadRecentInput) { ? ? ?const firstSessionEntry = sessionEntries[0]; ? ? ?const lastSessionEntry = sessionEntries[sessionEntries.length - 1]; ? ? ?// 如果条目与上一条目的相隔时间小于 1 秒且 ? ? ?// 与会话中第一个条目的相隔时间小于 5 秒,那么将条目 ? ? ?// 包含在当前会话中。否则,开始一个新会话。 ? ? ?if (sessionValue & ? ? ? ? ?entry.startTime - lastSessionEntry.startTime clsValue) { ? ? ? ?clsValue = sessionValue; ? ? ? ?clsEntries = sessionEntries; ? ? ? ?// 将更新值(及其条目)记录在控制台中。 ? ? ? ?console.log('CLS:', clsValue, clsEntries) ? ? ?} ? ?} ?}}).observe({type: 'layout-shift', buffered: true});4.3web-vitals Javascript 库? ? ? ? GoogleChrome 提供了 web-vitals JavaScript 开源库,用于测量用户浏览器端真实性能,除 CLS 指标外还包括 FID(First Input Delay)、TTFB(Time to First Byte)、FCP(First Contentful Paint)、LCP(Largest Contentful Paint)、INP(Interaction to next Paint)。代码示例:import { onCLS } from 'web-vitals';onCLS((metric) => { ?console.log(metric);});5. CLS 采集与上报? ? ? ? 通过使用 web-vitals JavaScript 库,我们可以便捷地获取页面的 CLS 和其他性能指标,这在很大程度上简化了数据测量工作。接下来,为了收集用户页面的真实 CLS 性能数据,我们可以设计和开发一套数据采集 sdk,将采集到的数据上报至服务器端进行存储。随后,通过后端大数据分析展示页面的整体 CLS 性能水平,从而为开发人员提供有针对性的优化建议。5.1采集、上报时机? ? ? ? 在数据采集和上报过程中,我们应确保数据的准确性和完整性,同时也要充分考虑对采集上报时机的设置,这样可以确保对整体数据统计评分的公平性。? CLS 的"时间窗口"为 5 秒,因此在 sdk 初始化后的第 5 秒,我们将页面性能数据进行数据采集与上报。然而,sdk 作为一个独立的 JS 文件,可以在页面中通过同步或异步加载。特别是在异步加载方式下,如果延迟 5 秒后再进行采集上报,很可能已超过第一个 CLS "时间窗口"。这样的数据采集可能会影响最终统计评分的公平性,其影响主要体现在以下两个方面:? 大于 5 秒上报? ? 受用户停留页面的时间因素影响,时间越靠后用户数据丢失的概率就越大。? ? ?如上图所示,采集工具 js(红框内) 下载时机在第 3.3 秒开始下载,如果在此基础上再延迟 5 秒后采集上报数据,真实的采集时间为 8.3 秒,假如用户停留时长小于 8.3 秒,此情况下若不采取措施该采样数据将会遗漏掉。? 小于 5 秒上报? ? 相反,如果将延时时间设定为小于 5 秒(如参考 Sentry@7.64.0 的默认设定时间 1 秒),所得的测量值准确性将得不到保证。为此我们用 web-vitals 做了一次数据分析对比实验,在日均 pv 约 10 万左右的网站上持续 3 天对 CLS 数据进行测量,并分别设定在 5 秒和 1 秒后采集和上报。对比结果发现 1 秒上报其各项指标数据丢失率非常大,且数据值偏小,无法保证其准确性。如下图所示:? 最优方案? ? 为了解决 sdk 初始化时间大于或小于第一个 CLS "时间窗口"所产生的影响,我们通过研究 web-vitals 源码发现 CLS 以及其他性能指标以 PerformanceNavigationTiming 的 "startTime" 为相对起点。因此,可以将 sdk 初始化时间与该 "startTime" 相减。如果两者的差值大于 5 秒,则可以立即进行数据采集与上报;反之,可以将距离 5 秒的差值作为延迟等待时间。? ? ?PerformanceNavigationTiming 模型如下图所示:? 页面关闭时采集上报? ? 当用户停留时长不足 5 秒提前关闭页面时,可能导致数据采集丢失。为了解决这个问题,我们可以通过监听 visibilitychange、pageHide 事件来采集页面关闭前的 CLS 数据。这种方案尽可能地确保数据不丢失,但无法保证第一个"时间窗口"的 5 秒准确度。? 因此,该方案所采集上报的数据只是尽可能的反映页面真实采样 PV,并不参与最终的统计评分。5.2采集流程设计? ? ? ? 因为 CLS 值的获取是一个持续进行的过程,在监听 DOM 布局偏移过程中,数据对象通过异步回调方式传递。这个持续动作可能会贯穿整个页面生命周期。由于获取到的数据需要上报到服务器端,不断的进行数据上报将对后端服务带来巨大压力。因此,需要设计一个合理的采集流程以避免上述问题。? ? ? ? 采集流程如下图所示:? ?1) sdk 初始化时已满足 5 秒,则立即执行 CLS 数据采集并上报,反之延迟至 5 秒再完成采集上报。? 2)用户停留时长不足 5 秒时页面关闭,监听 visibilitychange、pageHide 事件完成采集,并通过 sendBeacon 方式上报。? ?3)?整个页面生命周期中只允许一次采集、上报。5.3数据上报? ? ? ? 在上面流程设计图中我们设计了两种上报方式,sdk 初始化后 5 秒上报采用 XmlHttpRequest,页面隐藏或关闭采用 navigator.sendBeacon。? ? ? ? 两种方式详细对比见下图:5.4CLS 元素辅助定位? ? ? ? 在发现网站页面的 CLS 值较高时,我们需要使用开发的 sdk 协助开发人员将 CLS 值与其 DOM 元素关联起来,以便确定页面中累计偏移较大的元素。Web-vitals v3.0.0 及更高版本中新增了 attribution 调试功能,能够将 DOM 元素与 CLS 值关联并输出。然而,这导致了包体积增加了 7K,并且输出内容格式固定,无法实现定制化。? ? ? ? 为了定位导致 CLS 值过大的元素,同时防止 js 库体积变大,我们对 web-vitals 中 onCLS 返回的 layout-shift 条目对象进行自定义解析,并对 DOM 元素的 “domPath”路径输出,既帮助开发人员快速定位、修复问题,又缩小了 js 库体积。? ? ? ? 如图:6. 优化实践? ? ? ? 利用 sdk 我们已收集取到了页面真实数据,下面通过一个真实案例分享我们是如何进行 CLS 优化的。6.1页面结构介绍? ? ? ? 为了便于大家对页面结构有清晰的了解,我们在优化之前对页面结构按功能做了如下划分。1)? 文章内容区(Vue 渲染)。2)? 广告部分(之家广告 js 加载)。3)? 页头页尾(公共 js 加载)。4)? 其它(各类第三方 js 库,如统计、埋点、前端各类组件等)。如图所示:6.2页面逻辑说明本项目采用 SSR + Vue 实现前端页面渲染,在功能职责上 SSR 仅提供一个空页面模版,其主要内容的渲染工作由 Vue 承担,广告位的渲染部分被包含其中,广告位渲染链路为:第一步,渲染文章内容部分,由 Vue 绑定数据异步实现。第二步,判断有无广告位,先由 ajax 异步请求 isHave 接口,判断是否存在广告位,如存在则动态下载广告位 js 及相关资源(图片、样式文件)。第三步,渲染广告位, 广告 js 及资源文件下载完成后,动态插入 DOM 并完成渲染。6.3页面问题说明? Vue 渲染在广告位 js 及相关资源的加载之后,且广告位资源的下载需要等待 isHave 异步请求返回后,导致广告内容的渲染过程链路被拉长。其次广告位内容尺寸未知,因此页面在渲染过程中页面会因广告位的动态加载渲染出现一次“抖动”现象,最终导致页面的 CLS 值过大。下图动画演示了广告出现的整个渲染链路:6.4优化措施及内容在开始优化之前我们需要理顺各个资源的功能及依赖关系,并将资源根据功能做了如下划分:本部门公用资源公共 js 库。统计代码库。其它资源库及页面基础静态资源页头、页尾资源文件。广告库。页面静态资源(css、js)。优化思路为:让广告位提前并独立加载渲染,在 SSR 服务端渲染时保留广告位, 并在 SSR 渲染完文章部分后,第一时间由客户端发起请求,获取广告相关信息。相关优化措施如下:1)? 渐进式加载,优先保证首屏广告位提前渲染,渲染顺序为:文章内容(SSR)-> 广告(DOM传送门)-> Vue 应用 -> 页头页尾。2)? 广告渲染从 Vue 应用中剥离,并在 SSR 渲染时动态预留广告位 “DOM 传送门” 用以稳定 CLS,Vue 与广告位两者独立渲染,两者再无关联。3)? 广告位“DOM传送门”区域预设固定尺寸,当 dom 解析到某个广告坑位时,通过 js 将之前提前渲染好的广告移动到当前广告坑位。优化前后效果对比,如下图:6.5数据评分效果7. 总结通过本文我们对 CLS 进行了详细的阐述,并介绍了如何对 CLS 数值进行追踪获取,最后通过真实案例分享如何对 CLS 进行优化来提升用户体验。CLS的优化对网站性能和用户体验的提升是很重要的,除此之外它还包括以下几个方面的影响提升:?搜索引擎优化Google已经将 CLS 作为其评估网站性能的核心指标之一,因此优化CLS值对于提高搜索引擎排名非常重要,该指标的提升会吸引更多的流量。转化率页面元素的稳定性直接影响到用户在网站上采取行动的可能性。如果页面布局不稳定,用户可能会在尝试填写表单、点击按钮或完成购买等操作时遇到困难。因此,一个稳定的页面有助于用户完成目标操作,从而提升相关业务转化率。总之,CLS指标是衡量网站性能和用户体验的重要标准,优化CLS值可以提升用户体验、搜索引擎排名、转化率等,为网站带来长期收益。[参考引文]?
https://web.dev/optimize-cls/作者简介黄海荣经销商技术部-数据架构团队2019年加入经销商数据架构团队,参与前端工程化相关的工作,全程参与了 ftwo 前端监控系统,并推广至全公司前端应用。边凯经销商技术部-数据架构团队■2022年加入经销商数据架构团队,主要负责前端性能优化, 架构和工程化相关工作。阅读更多:页面坏UV性能优化Flutter For Web性能优化和新场景探索TestDataLake在提升测试效能方面的实践