在 Next.js 支持的电子商务网站中解决 CLS 问题(案例研究)
已发表: 2022-03-10Fairprice 是新加坡最大的在线杂货店之一。 我们一直在寻找机会来改善用户的在线购物体验。 性能是确保我们的用户无论使用何种设备或网络连接都能获得令人愉悦的用户体验的核心方面之一。
有许多关键性能指标 (KPI) 可以衡量网页生命周期中的不同点(例如 TTFB、 domInteractive
和onload
),但这些指标并不能反映最终用户对页面的体验。
我们想使用一些与最终用户的实际体验密切对应的 KPI,因此我们知道,如果这些 KPI 中的任何一个表现不佳,那么它将直接影响最终用户的体验。 我们发现以用户为中心的性能指标非常适合此目的。
有许多以用户为中心的性能指标来衡量页面生命周期中的不同点,例如 FCP、LCP、FID、CLS 等。 对于本案例研究,我们将主要关注 CLS。
CLS 测量页面开始加载到卸载之间发生的所有意外布局转换的总分。
“
因此,具有较低的 CLS 值的页面可确保没有随机的布局变化导致用户沮丧。 Barry Pollard 写了一篇关于 CLS 的优秀的深入文章。
我们如何在产品页面中发现 CLS 问题
我们使用 Lighthouse 和 WebPagetest 作为我们的综合测试工具来衡量 CLS 的性能。 我们还使用 web-vitals 库来衡量真实用户的 CLS。 除此之外,我们还会查看 Google Search Console Core Web Vitals Report 部分,以了解我们任何页面中的任何潜在 CLS 问题。 在探索报告部分时,我们发现产品详细信息页面中的许多 URL 的 CLS 值超过0.1 ,这表明那里发生了一些重大的布局转换事件。
使用不同工具调试 CLS 问题
现在我们知道产品详细信息页面上存在 CLS 问题,下一步是确定导致该问题的元素。 起初,我们决定使用综合测试工具运行一些测试。
所以我们运行灯塔检查它是否能找到任何可能触发重大布局转变的元素,它报告 CLS 为 0.004,非常低。
Lighthouse 报告页面有一个诊断部分。 这也没有显示任何导致高 CLS 值的元素。
然后我们运行 WebpageTest 并决定检查幻灯片视图:
我们发现此功能非常有用,因为我们可以找出哪个元素在哪个时间点导致布局发生变化。 但是当我们运行测试以查看是否突出显示任何布局变化时,没有任何东西对巨大的 LCS 有任何贡献:
CLS 的怪癖是它在页面的整个生命周期中记录单个布局转换分数并添加它们。
“
注意:自 2021 年 6 月起,CLS 的测量方式发生了变化。
由于 Lighthouse 和 WebpageTest 无法检测到任何触发重大布局变化的元素,这意味着它发生在初始页面加载之后可能是由于某些用户操作。 所以我们决定使用 Web Vitals Google Chrome 扩展,因为它可以在用户与之交互时在页面上记录 CLS。 在执行不同的操作后,我们发现当用户使用图像放大功能时,布局偏移分数正在增加。
为了交叉验证鼠标悬停在图像上时是否发生布局移位,我们使用了来自 https://web.dev/cls/ 的以下代码片段,它在发生布局移位时添加了console.log
:
let cls = 0; new PerformanceObserver((entryList) => { for (const entry of entryList.getEntries()) { if (!entry.hadRecentInput) { cls += entry.value; console.log('Current CLS value:', cls, entry); } }}).observe({type: 'layout-shift', buffered: true});
在进一步调查中,我们发现 ASDA 面临类似的问题,并针对 chrome 提出了该问题。
根本原因
在产品详细信息页面上,用户可以将鼠标移到产品图像上,以与实际产品图像并排查看图像的放大部分,因为该视频准确地展示了我们正在谈论的内容。
图像放大功能帮助我们的用户获得产品的外观和感觉,并确保它是他们想要购买的产品的正确变体。
我们已经使用react-image zoom
库来构建这个图像放大功能。
Image Magnify 库通常有一个镜头(当鼠标在图像中移动时移动的正方形)。 由于此镜头随着鼠标移动而改变其顶部和左侧位置,因此它被检测为触发 CLS 的布局移位。 我们检查了库页面以及其他类似的 React 库( react-image-magnify
、 react-image-zoom
、 react-image-magnifiers
),发现它们都存在相同的 CLS 问题。
我们如何修复它
我们注意到react-image-zoom
正在使用js-image-zoom
库。 所以我们不得不修改js-image zoom
库来解决这个问题。
解决方案非常简单。 当鼠标在产品图像上移动时,图像镜头元件通过改变其左侧和顶部位置来移动。 为了解决这个问题,我们使用了transform translate
将元素移动到一个新层,即在这个新层上发生的任何移动都不会再导致布局移位:
我还为原始存储库创建了一个 PR,以便使用此库的其他开发人员可以摆脱 CLS 问题。
变化的影响
代码部署到生产环境后,CLS 被固定在产品详情页面,受 CLS 影响的页面数量减少了 98%:
由于我们使用了transform
,它还有助于使图像放大给用户带来更流畅的体验。
注意: Paul Irish 写了一篇关于这个主题的优秀文章。
我们为 CLS 所做的其他关键更改
在我们网站的许多页面中,我们还遇到了一些其他有助于 CLS 的问题。 让我们来看看这些元素和组件,看看我们如何尝试减轻由它们引起的布局变化。
网络字体:
我们注意到,字体的延迟加载会导致用户感到沮丧,因为内容会闪烁,并且还会导致一些布局变化。 为了尽量减少这种情况,我们做了一些更改:- 我们已经自行托管了字体,而不是从第 3 方 CDN 加载。
- 我们预加载字体。
- 我们使用字体显示可选。
图片:
图像中缺少高度或宽度值会导致图像后的元素在图像加载后发生移动。 这最终成为 CLS 的主要贡献者。 由于我们使用的是 Next.js,因此我们利用了名为next/images
的内置图像组件。 该组件包含几个与图像相关的最佳实践。 它建立在<img>
HTML 标签之上,可以帮助改进 LCP 和 CLS。 我强烈建议阅读此 RFC,以了解使用它的关键特性和优势。无限滚动:
在我们的网站上,产品列表页面可以无限滚动。 因此,最初,当用户滚动到页面底部时,他们会在加载下一组数据之前看到页脚的几分之一秒,这会导致布局发生变化。 为了解决这个问题,我们采取了几个步骤:- 我们甚至在用户到达列表的绝对底部之前调用 API 来加载数据。
- 我们为加载状态预留了足够的空间,并在加载状态显示产品骨架。 所以现在当用户滚动时,他们在加载产品的几秒钟内看不到页脚。
Addy Osmani 写了一篇关于这种方法的详细文章,我强烈建议您查看。
关键要点
- 虽然 Lighthouse 和 WebpageTest 有助于发现页面加载前发生的性能问题,但它们无法检测页面加载后的性能问题。
- Web Vitals 扩展可以检测由用户交互触发的 CLS 更改,因此如果页面具有高 CLS 值但 Lighthouse 或 WebpageTest 报告低 CLS,则 Web Vitals 扩展可以帮助查明问题。
- Google Search Console 数据基于真实用户的数据,因此也可以指出页面生命周期中任何时候发生的潜在性能问题。 一旦检测到并修复了问题,再次检查报告部分可以帮助验证性能修复的有效性。 这些更改会在几天内反映在 Web Vitals 报告部分中。
最后的想法
虽然 CLS 问题相对难以调试,但在页面加载(Lighthouse、WebPageTest)和 Web Vitals 扩展(页面加载后)之前使用不同工具的组合可以帮助我们查明问题。 它也是正在经历大量积极开发以涵盖广泛场景的指标之一,这意味着它的衡量方式将在未来发生变化。 我们正在关注 https://web.dev/evolving-cls/ 以了解任何即将发生的变化。
至于我们,我们也在不断努力改进其他 Core Web Vitals。 最近,我们实现了响应式图像预加载并开始以 WebP 格式提供图像,这帮助我们减少了 75% 的图像负载,减少了 62% 的 LCP,以及 24% 的速度指数。 您可以阅读有关改进 LCP 和速度指数的优化的更多详细信息,或关注我们的工程博客以了解我们正在进行的其他令人兴奋的工作。
我们要感谢 Alex Castle 帮助我们在产品页面上调试 CLS 问题并解决了next/images
实现中的怪癖。