前端性能优化之LCP指标提升

1,133 阅读9分钟

背景

在实际的项目需求研发中,经常会得到测试或者业务同学反馈项目部分页面访问加载白屏时间比较长,或者页面呈现比较慢等体验问题。但是通过项目整体的评分数据分析又是比较正常的,这个时候我们不妨去看看所反馈具体页面的 LCP 指标,是不是得分偏低?以下则是针对项目页面 LCP 性能优化的一次探索。

什么是 LCP

Largest Contentful Paint (LCP) 是一项重要且稳定的 Core Web Vitals 指标,渲染了足够的内容以使用户可以参与其中,用于衡量用户感知的加载速度,快速 LCP 有助于让用户确信页面有用。LCP 是以页面为维度的评测指标,为了提供良好的用户体验,网站应力求至少有 75% 的网页访问的 LCP 不超过 2.5 秒。

如下所示,它可报告视口中可见的最大图片或文本块的呈现时间 (相对于用户首次转到相应网页的时间),接近页面的视觉完成度,让用户感知页面有用。

如何衡量 LCP 指标

工欲善其事,必先利其器,一个好的测量方法或者工具能让你事半功倍。要先知道在每个阶段怎么去准确获得指标值,才能有针对性地去进行页面优化。

生产环境阶段

可以在 JavaScript 中使用 PerformanceObserver API 或者直接使用 web-vitals 库获取当前页面的 LCP 分值,进而采集到生产环境用户端真实的性能数据。

// 使用PerformanceObserver API
new PerformanceObserver((entryList) => {
  for (const entry of entryList.getEntries()) {
    console.log('LCP candidate:', entry.startTime, entry);
  }
}).observe({ type: 'largest-contentful-paint', buffered: true });
// 使用web-vitals
import { onLCP } from 'web-vitals';
onLCP(console.log);

虽然 PageSpeed Insights 或者 WebPageTest 等工具来模拟不同的设备和网络环境分析网站在不同场景的页面性能,这种工具在分析单个页面时可以使用。如果你想要评估整个项目上线后实际的性能数据,可以基于 web-vitals 去采集运行过程中的性能指标后上报。基于这些上报的数据,可以对项目的做有针对性的优化。

开发环境阶段

在实际开发阶段,还可以使用浏览器自带的 Chrome DevTools Performance 或者 Lighthouse 来分析具体页面的加载详情,如下是利用上述工具分析项目案例页面得到的模拟数据,左下是 Performance,右边是 Lighthouse。

至于 Lighthouse 分析得到的 LCP 数据为什么会比 Performance 差那么多,是因为 Lighthouse 默认启用模拟的网络节流和 CPU 节流。

参考资料:stackoverflow.com/questions/6…

案例优化实践

接下来我们直奔主题,拿到上述这个案例分析数据后完成一次 LCP 优化实践。

项目是客户端渲染模式,模拟的过程禁止任何缓存,其实从上述的 Performance 和 Lighthouse 都可以观察到,页面的 LCP 瓶颈基本是在 Load Delay 阶段,集中力量针对 Load Delay 阶段进行优化就可以取得最大的收益。

再从 Performance 的 Network 层具体分析,主要分为 A(HTML,850ms),B(JS 及 CSS 资源,1300ms),C(API 网络请求,950ms),D(LCP 资源加载,100ms) 四个加载阶段,最后页面呈现的 LCP 资源是一张图片,对应的 D 位置,LCP 时间是 3.38s,优化目标是控制在 2.5s 之内,当然速度越快用户体验就越好。 再从 Performance 的 Network 层具体分析,主要分为 A(HTML,850ms),B(JS 及 CSS 资源,1300ms),C(API 网络请求,950ms),D(LCP 资源加载,100ms) 四个加载阶段,最后页面呈现的 LCP 资源是一张图片,对应的 D 位置,LCP 时间是 3.38s,优化目标是控制在 2.5s 之内,当然速度越快用户体验就越好。

HTML 页面加载暂时不在前端此次优化的范畴,延迟阻塞比较严重的 B 和 C 阶段实际对应了 Lighthouse 里的 Load Delay,可以优先从该阶段着手。

项目是基于 umi 前端框架,在 HTML 页面加载完成后,开始加载页面内的 JS 和 CSS 资源,整个过程足足持续了 1.3s,这里可以尝试两个方向,一是让阻塞主流程的资源包资源变小 (拆包或者资源压缩等),而是优化对 LCP 资源有影响的关键网络路径。

针对资源包的优化在各个框架里大同小异,为避免 bundle 体积过大影响下载速度,umi 里可以利用 externals 配置剥离出可以从 CDN 引入的 JS 库,或者利用 links 配置可预加载部分刚需的 CSS 或 JS。

{
  externals: {
    react: 'window.React',
  },
  scripts: [
    'https://static.xxx.cn/libs/react/17.0.2/umd/react.production.min.js',
  ],
}
{
  links: [
    { href: '/xxx.css', rel: 'preload' }, // css预加载
    { href: 'http://localhost:8000/xxx.js', rel: 'preload', as: 'script' }, // JS预加载
  ],
}

除了常规的 JS 库拆包,图片的引入方式也要注意,大图做好压缩并使用 CDN 引入,必要时可以做懒加载,避免都编译为 Base64。同时配置好 Tree shaking 解决冗余文件引入的问题。以下为主流程包体积优化后的效果,可以看到 B 阶段已经从原先的 1300ms 下降到 650ms 左右。

C 阶段的起点刚好是 FCP 生成的时间,这时候页面开始发起业务 API 网络请求,距离 LCP 资源加载仍有 1s 左右的时间,这里存在一个严重的问题,几个阶段的网络请求是串行的,各地区网络环境参差不齐,一不小心就会引起资源阻塞。实际分析可以归纳出能并行发起的请求类,剔除对 LCP 资源有影响的阻塞部分逻辑。

改造后 C 阶段从 1s 提升到 400ms,总体的 LCP 分数 (1.78s) 也已经达成 2.5s 以内的目标。

那么还有更大的优化空间吗,答案是肯定的。案例的 LCP 资源是一个图片,图片本身做好压缩,可以适做图片预加载,将图片加载占用的 100ms 多的时间也节省出来。

在项目优化过程中,对于非 LCP 的图片资源可以做懒加载,若图像是 LCP 资源,这将是个灾难,在页面中优先考虑的应该是预加载。

项目若是使用服务端渲染,理论上还可以将上述的 LCP 资源加载拉到 A 阶段加载完成后的时间,页面视觉完成度呈现得更快。

如何让你的元素成为 LCP 资源

在上述案例中,D 阶段的 LCP 资源优化固然重要,但往往影响 LCP 最严重的并不是这个,而是占据总时长 80% 的 Load Delay 阶段,在 Load Delay 阶段中,LCP 图片依赖业务 API 请求的响应来决定渲染时机。

实际上如果 LCP 资源不依赖网络层的业务请求,而是在页面开始渲染阶段就开始加载,LCP 的效果会更好。因此又能引出一个新的问题:怎样使元素合理地成为页面的 LCP 资源。

其实网页在加载过程中,最大的元素是随内容加载而持续变化的。

因此怎么选取合适的网页元素成为页面的 LCP 资源,一定程度也决定了项目的优化方向。下面报表是实践中总结出来的现阶段的关键信息,在未来,评判标准还会继续改进变化。

关键信息判断结论
决定最大内容块的元素类型?img, video ,svg 内的 image 元素, 通过url()加载背景图片的元素,包括文本节点或者其他内敛文本元素子级的块级元素
决定元素大小?用户在视口中可见的大小,如元素超出在视口之外,超出部分不会计入元素大小
何时报告最大内容块?首次内容绘制开始到用户与页面互动(点击,滚动,按键等)为止

一些文章有建议避免基于图像的 LCP,获得快速 LCP 的最佳方法是确保您的 LCP 是基于文本的。我觉得又不太准确,应该是避免您的 LCP 资源出现时机是基于冗长的业务逻辑判断,这是在需求评审以及系统架构设计时需多加思考的问题。完美的 LCP 呈现时间应该是略大于 FCP 出现的时机,但这也是理想状态,在具体的业务场景中往往会表现得更加复杂。

有前辈也做过使用不同方式加载图片的实验,会有略微的差异,但这仅限于极端环境的表现,感兴趣可适当了解。在实际项目中,各种 CDN 及缓存方案的加持,这些差异也将忽略不计,使用何种方式加载图片较合理应更多参考业务自身而不是为了性能而强加性能。

总结

所以相比常规的 FP,FCP,FID 等性能指标的优化,因为受各个项目复杂因素影响较多,LCP 的优化提升往往会表现得难以下手。但归根到一点,所有的指标优化都是基于用户体验,实际做的也是也是其他指标类似的优化内容,最好的方式就是来一次实践。

简单做下 LCP 影响因素及优化策略的总结:

影响因素

服务器响应时间,渲染阻塞的 JS 和 CSS,资源加载时间,客户端渲染

优化策略

  • 优化关键渲染路径
  • 尽快加载和渲染页面最大块元素
  • 删除未使用的 CSS,JS,减少包体积大小
  • 资源压缩,优化资源 (CSS,JS,IMG 等)
  • 采用 PRPL 模式应用即时加载
  • 服务端渲染

页面可能不能做得更快,但你可以让用户感觉更快。